1use chess::{Board, ChessMove, File, MoveGen, Piece, Rank, Square};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5#[derive(Debug, Clone)]
12pub struct StrategicEvaluator {
13 config: StrategicConfig,
14 attacking_patterns: HashMap<String, AttackingPattern>,
15 positional_plans: Vec<PositionalPlan>,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct StrategicConfig {
21 pub initiative_weight: f32,
23
24 pub attacking_piece_bonus: f32,
26
27 pub dynamic_factor_weight: f32,
29
30 pub pawn_structure_planning: bool,
32
33 pub king_attack_square_bonus: f32,
35
36 pub piece_coordination_weight: f32,
38
39 pub enable_imbalance_creation: bool,
41}
42
43impl Default for StrategicConfig {
44 fn default() -> Self {
45 Self {
46 initiative_weight: 0.4, attacking_piece_bonus: 10.0, dynamic_factor_weight: 0.3, pawn_structure_planning: true, king_attack_square_bonus: 8.0, piece_coordination_weight: 0.4, enable_imbalance_creation: false, }
54 }
55}
56
57impl StrategicConfig {
58 pub fn aggressive() -> Self {
60 Self {
61 initiative_weight: 0.8, attacking_piece_bonus: 25.0, dynamic_factor_weight: 0.6, king_attack_square_bonus: 15.0, piece_coordination_weight: 0.7, enable_imbalance_creation: true,
67 ..Default::default()
68 }
69 }
70
71 pub fn balanced() -> Self {
73 Self {
74 initiative_weight: 0.3, attacking_piece_bonus: 8.0, dynamic_factor_weight: 0.25, king_attack_square_bonus: 6.0, piece_coordination_weight: 0.5, enable_imbalance_creation: false, ..Default::default()
81 }
82 }
83
84 pub fn positional() -> Self {
86 Self {
87 initiative_weight: 0.2, attacking_piece_bonus: 5.0, dynamic_factor_weight: 0.15, pawn_structure_planning: true, king_attack_square_bonus: 3.0, piece_coordination_weight: 0.7, enable_imbalance_creation: false, }
95 }
96
97 pub fn safety_first() -> Self {
99 Self {
100 initiative_weight: 0.15, attacking_piece_bonus: 3.0, dynamic_factor_weight: 0.1, pawn_structure_planning: true, king_attack_square_bonus: 2.0, piece_coordination_weight: 0.8, enable_imbalance_creation: false, }
108 }
109
110 pub fn master_level() -> Self {
112 Self {
113 initiative_weight: 0.25, attacking_piece_bonus: 6.0, dynamic_factor_weight: 0.2, pawn_structure_planning: true, king_attack_square_bonus: 5.0, piece_coordination_weight: 0.6, enable_imbalance_creation: false, }
121 }
122}
123
124#[derive(Debug, Clone)]
126pub struct AttackingPattern {
127 pub name: String,
128 pub description: String,
129 pub initiative_bonus: f32, pub required_pieces: Vec<Piece>, pub target_squares: Vec<Square>, pub coordination_bonus: f32, }
134
135#[derive(Debug, Clone)]
137pub struct PositionalPlan {
138 pub name: String,
139 pub goal: PlanGoal,
140 pub required_moves: Vec<ChessMove>, pub evaluation_bonus: f32, pub urgency: PlanUrgency, }
144
145#[derive(Debug, Clone, PartialEq)]
146pub enum PlanGoal {
147 AttackKing, ControlCenter, CreateWeaknesses, PieceCoordination, PawnStructure, InitiativeSeizure, }
154
155#[derive(Debug, Clone, PartialEq)]
156pub enum PlanUrgency {
157 Immediate, High, Medium, Low, }
162
163#[derive(Debug, Clone)]
165pub struct StrategicEvaluation {
166 pub base_evaluation: f32, pub initiative_bonus: f32, pub attacking_bonus: f32, pub coordination_bonus: f32, pub plan_bonus: f32, pub total_evaluation: f32, pub recommended_plan: Option<PositionalPlan>, pub attacking_moves: Vec<ChessMove>, pub positional_moves: Vec<ChessMove>, }
176
177impl StrategicEvaluator {
178 pub fn new(config: StrategicConfig) -> Self {
180 let mut evaluator = Self {
181 config,
182 attacking_patterns: HashMap::new(),
183 positional_plans: Vec::new(),
184 };
185
186 evaluator.initialize_attacking_patterns();
187 evaluator.initialize_positional_plans();
188
189 evaluator
190 }
191
192 pub fn new_default() -> Self {
194 Self::new(StrategicConfig::default())
195 }
196
197 pub fn aggressive() -> Self {
199 Self::new(StrategicConfig::aggressive())
200 }
201
202 pub fn evaluate_strategic(&self, board: &Board) -> StrategicEvaluation {
204 let base_evaluation = self.calculate_base_evaluation(board);
205 let initiative_bonus = self.evaluate_initiative(board);
206 let attacking_bonus = self.evaluate_attacking_potential(board);
207 let coordination_bonus = self.evaluate_piece_coordination(board);
208 let (plan_bonus, recommended_plan) = self.evaluate_strategic_plans(board);
209
210 let safety_penalty = self.evaluate_safety_concerns(board);
212
213 let total_evaluation = base_evaluation
214 + (initiative_bonus * self.config.initiative_weight)
215 + attacking_bonus
216 + (coordination_bonus * self.config.piece_coordination_weight)
217 + plan_bonus
218 - safety_penalty; let attacking_moves = self.generate_safe_attacking_moves(board);
221 let positional_moves = self.generate_safe_positional_moves(board);
222
223 StrategicEvaluation {
224 base_evaluation,
225 initiative_bonus,
226 attacking_bonus,
227 coordination_bonus,
228 plan_bonus,
229 total_evaluation,
230 recommended_plan,
231 attacking_moves,
232 positional_moves,
233 }
234 }
235
236 pub fn generate_proactive_moves(&self, board: &Board) -> Vec<(ChessMove, f32)> {
238 let strategic_eval = self.evaluate_strategic(board);
239 let mut proactive_moves = Vec::new();
240
241 for mv in strategic_eval.attacking_moves {
243 let move_value = self.evaluate_move_initiative(board, &mv);
244 let safety_penalty = self.evaluate_move_safety(board, &mv);
245 let final_value = move_value - safety_penalty; if final_value > 50.0 {
248 proactive_moves.push((mv, final_value));
250 }
251 }
252
253 for mv in strategic_eval.positional_moves {
255 let move_value = self.evaluate_move_positional(board, &mv);
256 let safety_penalty = self.evaluate_move_safety(board, &mv);
257 let final_value = move_value - safety_penalty;
258
259 if final_value > 30.0 {
260 proactive_moves.push((mv, final_value));
262 }
263 }
264
265 proactive_moves.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
267
268 proactive_moves.truncate(10);
270
271 proactive_moves
272 }
273
274 pub fn should_play_aggressively(&self, board: &Board) -> bool {
276 let strategic_eval = self.evaluate_strategic(board);
277
278 strategic_eval.initiative_bonus > 0.0
283 && strategic_eval.coordination_bonus > 0.0
284 && self.is_enemy_king_attackable(board)
285 }
286
287 pub fn blend_with_hybrid_evaluation(
289 &self,
290 board: &Board,
291 nnue_eval: f32,
292 pattern_eval: f32,
293 ) -> f32 {
294 let strategic_eval = self.evaluate_strategic(board);
295
296 let strategic_weight = if self.should_play_aggressively(board) {
298 0.4
299 } else {
300 0.2
301 };
302 let nnue_weight = 0.5;
303 let pattern_weight = 0.3;
304
305 (strategic_eval.total_evaluation * strategic_weight)
306 + (nnue_eval * nnue_weight)
307 + (pattern_eval * pattern_weight)
308 }
309
310 fn initialize_attacking_patterns(&mut self) {
313 self.attacking_patterns.insert(
315 "king_attack_setup".to_string(),
316 AttackingPattern {
317 name: "King Attack Setup".to_string(),
318 description: "Pieces coordinated for king attack".to_string(),
319 initiative_bonus: 50.0,
320 required_pieces: vec![Piece::Queen, Piece::Rook],
321 target_squares: vec![], coordination_bonus: 25.0,
323 },
324 );
325
326 self.attacking_patterns.insert(
328 "central_pressure".to_string(),
329 AttackingPattern {
330 name: "Central Pressure".to_string(),
331 description: "Control center and create attacking chances".to_string(),
332 initiative_bonus: 30.0,
333 required_pieces: vec![Piece::Knight, Piece::Bishop],
334 target_squares: vec![Square::E4, Square::E5, Square::D4, Square::D5],
335 coordination_bonus: 15.0,
336 },
337 );
338
339 self.attacking_patterns.insert(
341 "piece_coordination".to_string(),
342 AttackingPattern {
343 name: "Piece Coordination".to_string(),
344 description: "Multiple pieces working together".to_string(),
345 initiative_bonus: 25.0,
346 required_pieces: vec![Piece::Bishop, Piece::Knight],
347 target_squares: vec![],
348 coordination_bonus: 20.0,
349 },
350 );
351 }
352
353 fn initialize_positional_plans(&mut self) {
354 self.positional_plans.push(PositionalPlan {
355 name: "King Attack".to_string(),
356 goal: PlanGoal::AttackKing,
357 required_moves: vec![], evaluation_bonus: 100.0,
359 urgency: PlanUrgency::High,
360 });
361
362 self.positional_plans.push(PositionalPlan {
363 name: "Control Center".to_string(),
364 goal: PlanGoal::ControlCenter,
365 required_moves: vec![],
366 evaluation_bonus: 50.0,
367 urgency: PlanUrgency::Medium,
368 });
369
370 self.positional_plans.push(PositionalPlan {
371 name: "Seize Initiative".to_string(),
372 goal: PlanGoal::InitiativeSeizure,
373 required_moves: vec![],
374 evaluation_bonus: 75.0,
375 urgency: PlanUrgency::Immediate,
376 });
377 }
378
379 fn calculate_base_evaluation(&self, board: &Board) -> f32 {
380 let mut eval = 0.0;
383
384 for square in chess::ALL_SQUARES {
386 if let Some(piece) = board.piece_on(square) {
387 let piece_value = match piece {
388 Piece::Pawn => 100.0,
389 Piece::Knight => 320.0,
390 Piece::Bishop => 330.0,
391 Piece::Rook => 500.0,
392 Piece::Queen => 900.0,
393 Piece::King => 0.0,
394 };
395
396 if board.color_on(square) == Some(board.side_to_move()) {
397 eval += piece_value;
398 } else {
399 eval -= piece_value;
400 }
401 }
402 }
403
404 eval / 100.0 }
406
407 fn evaluate_initiative(&self, board: &Board) -> f32 {
408 let mut initiative = 0.0;
409
410 let our_moves = MoveGen::new_legal(board).count() as f32;
412
413 let opponent_mobility = self.estimate_opponent_mobility(board);
415
416 let mobility_advantage = our_moves - opponent_mobility;
418 initiative += mobility_advantage * 2.0;
419
420 if self.is_position_sharp(board) {
422 initiative += 20.0;
423 }
424
425 initiative += self.evaluate_central_control(board) * 10.0;
427
428 initiative
429 }
430
431 fn evaluate_attacking_potential(&self, board: &Board) -> f32 {
432 let mut attack_bonus = 0.0;
433 let enemy_king_square = self.get_enemy_king_square(board);
434
435 if let Some(king_square) = enemy_king_square {
436 let attacking_pieces = self.count_pieces_attacking_king_area(board, king_square);
438 attack_bonus += attacking_pieces as f32 * self.config.attacking_piece_bonus;
439
440 let controlled_attack_squares =
442 self.count_controlled_king_area_squares(board, king_square);
443 attack_bonus += controlled_attack_squares as f32 * self.config.king_attack_square_bonus;
444 }
445
446 attack_bonus
447 }
448
449 fn evaluate_piece_coordination(&self, board: &Board) -> f32 {
450 let mut coordination = 0.0;
451
452 for square in chess::ALL_SQUARES {
454 if let Some(_piece) = board.piece_on(square) {
455 if board.color_on(square) == Some(board.side_to_move()) {
456 coordination += self.count_piece_supporters(board, square) * 5.0;
457 }
458 }
459 }
460
461 coordination
462 }
463
464 fn evaluate_strategic_plans(&self, board: &Board) -> (f32, Option<PositionalPlan>) {
465 let mut best_plan_bonus = 0.0;
466 let mut best_plan = None;
467
468 for plan in &self.positional_plans {
469 let plan_feasibility = self.evaluate_plan_feasibility(board, plan);
470 let plan_value = plan.evaluation_bonus * plan_feasibility;
471
472 if plan_value > best_plan_bonus {
473 best_plan_bonus = plan_value;
474 best_plan = Some(plan.clone());
475 }
476 }
477
478 (best_plan_bonus, best_plan)
479 }
480
481 fn generate_safe_attacking_moves(&self, board: &Board) -> Vec<ChessMove> {
482 let mut attacking_moves = Vec::new();
483
484 for mv in MoveGen::new_legal(board) {
485 if self.is_move_attacking(&mv, board) {
486 let safety_penalty = self.evaluate_move_safety(board, &mv);
488 if safety_penalty < 50.0 {
489 attacking_moves.push(mv);
491 }
492 }
493 }
494
495 attacking_moves
496 }
497
498 fn generate_safe_positional_moves(&self, board: &Board) -> Vec<ChessMove> {
499 let mut positional_moves = Vec::new();
500
501 for mv in MoveGen::new_legal(board) {
502 if self.is_move_positional(&mv, board) {
503 let safety_penalty = self.evaluate_move_safety(board, &mv);
505 if safety_penalty < 30.0 {
506 positional_moves.push(mv);
508 }
509 }
510 }
511
512 positional_moves
513 }
514
515 fn estimate_opponent_mobility(&self, board: &Board) -> f32 {
518 let current_side = board.side_to_move();
520
521 let opponent_color = !current_side;
525 let mut opponent_mobility = 0;
526
527 for square in chess::ALL_SQUARES {
528 if board.color_on(square) == Some(opponent_color) {
529 if let Some(piece) = board.piece_on(square) {
530 for target_square in chess::ALL_SQUARES {
532 if self.can_piece_attack_square(board, square, piece, target_square) {
533 opponent_mobility += 1;
534 }
535 }
536 }
537 }
538 }
539
540 opponent_mobility as f32
541 }
542
543 fn is_position_sharp(&self, board: &Board) -> bool {
544 let mut sharp_indicators = 0;
546
547 let legal_moves = MoveGen::new_legal(board).count();
549 if legal_moves > 35 {
550 sharp_indicators += 1;
551 }
552
553 let mut pieces_under_attack = 0;
555 for square in chess::ALL_SQUARES {
556 if board.piece_on(square).is_some() && self.is_square_attacked_by_enemy(board, square) {
557 pieces_under_attack += 1;
558 }
559 }
560 if pieces_under_attack >= 3 {
561 sharp_indicators += 1;
562 }
563
564 if board.checkers().0 != 0 {
566 sharp_indicators += 2; }
568
569 let captures = MoveGen::new_legal(board)
571 .filter(|mv| board.piece_on(mv.get_dest()).is_some())
572 .count();
573 if captures >= 5 {
574 sharp_indicators += 1;
575 }
576
577 sharp_indicators >= 2 }
579
580 fn evaluate_central_control(&self, board: &Board) -> f32 {
581 let central_squares = [Square::E4, Square::E5, Square::D4, Square::D5];
582 let mut control = 0.0;
583
584 for square in central_squares {
585 if board.color_on(square) == Some(board.side_to_move()) {
586 control += 1.0;
587 }
588 }
589
590 control / central_squares.len() as f32
591 }
592
593 fn get_enemy_king_square(&self, board: &Board) -> Option<Square> {
594 let enemy_color = !board.side_to_move();
595 Some(board.king_square(enemy_color))
596 }
597
598 fn count_pieces_attacking_king_area(&self, board: &Board, king_square: Square) -> usize {
599 let king_area = self.get_king_area_squares(king_square);
601 let mut attacking_pieces = 0;
602
603 for square in chess::ALL_SQUARES {
604 if board.color_on(square) == Some(board.side_to_move()) {
605 if let Some(piece) = board.piece_on(square) {
606 if self.piece_attacks_king_area(board, square, piece, &king_area) {
607 attacking_pieces += 1;
608 }
609 }
610 }
611 }
612
613 attacking_pieces
614 }
615
616 fn count_controlled_king_area_squares(&self, board: &Board, king_square: Square) -> usize {
617 let king_area = self.get_king_area_squares(king_square);
618 let mut controlled = 0;
619
620 for area_square in king_area {
621 if self.do_we_control_square(board, area_square) {
622 controlled += 1;
623 }
624 }
625
626 controlled
627 }
628
629 fn get_king_area_squares(&self, king_square: Square) -> Vec<Square> {
630 let mut area_squares = Vec::new();
631 let king_file = king_square.get_file().to_index() as i8;
632 let king_rank = king_square.get_rank().to_index() as i8;
633
634 for file_offset in -1..=1 {
636 for rank_offset in -1..=1 {
637 let new_file = king_file + file_offset;
638 let new_rank = king_rank + rank_offset;
639
640 if (0..8).contains(&new_file) && (0..8).contains(&new_rank) {
641 let file = File::from_index(new_file as usize);
642 let rank = Rank::from_index(new_rank as usize);
643 let square_index = (rank.to_index() * 8 + file.to_index()) as u8;
644 let square = unsafe { Square::new(square_index) };
645 area_squares.push(square);
646 }
647 }
648 }
649
650 area_squares
651 }
652
653 fn piece_attacks_king_area(
654 &self,
655 board: &Board,
656 piece_square: Square,
657 piece: Piece,
658 king_area: &[Square],
659 ) -> bool {
660 for &target_square in king_area {
662 if self.can_piece_attack_square(board, piece_square, piece, target_square) {
663 return true;
664 }
665 }
666 false
667 }
668
669 fn do_we_control_square(&self, board: &Board, square: Square) -> bool {
670 let our_color = board.side_to_move();
672
673 for check_square in chess::ALL_SQUARES {
674 if board.color_on(check_square) == Some(our_color) {
675 if let Some(piece) = board.piece_on(check_square) {
676 if self.can_piece_attack_square(board, check_square, piece, square) {
677 return true;
678 }
679 }
680 }
681 }
682 false
683 }
684
685 fn count_piece_supporters(&self, board: &Board, square: Square) -> f32 {
686 let our_color = board.side_to_move();
688 let mut supporters = 0.0;
689
690 for check_square in chess::ALL_SQUARES {
691 if board.color_on(check_square) == Some(our_color) && check_square != square {
692 if let Some(piece) = board.piece_on(check_square) {
693 if self.can_piece_attack_square(board, check_square, piece, square) {
694 supporters += 1.0;
695 }
696 }
697 }
698 }
699 supporters
700 }
701
702 fn evaluate_plan_feasibility(&self, _board: &Board, plan: &PositionalPlan) -> f32 {
703 match plan.urgency {
705 PlanUrgency::Immediate => 1.0,
706 PlanUrgency::High => 0.8,
707 PlanUrgency::Medium => 0.6,
708 PlanUrgency::Low => 0.4,
709 }
710 }
711
712 fn is_move_attacking(&self, mv: &ChessMove, board: &Board) -> bool {
713 let dest = mv.get_dest();
715
716 if board.piece_on(dest).is_some() {
718 return true;
719 }
720
721 if let Some(piece) = board.piece_on(mv.get_source()) {
723 let temp_board = board.make_move_new(*mv);
725 let enemy_color = !board.side_to_move();
726
727 for enemy_square in chess::ALL_SQUARES {
728 if temp_board.color_on(enemy_square) == Some(enemy_color) && self.can_piece_attack_square(&temp_board, dest, piece, enemy_square) {
729 return true;
730 }
731 }
732 }
733
734 let forward_progress = if board.side_to_move() == chess::Color::White {
736 dest.get_rank().to_index() > mv.get_source().get_rank().to_index()
737 } else {
738 dest.get_rank().to_index() < mv.get_source().get_rank().to_index()
739 };
740
741 forward_progress && dest.get_rank().to_index() > 3 && dest.get_rank().to_index() < 4
742 }
743
744 fn is_move_positional(&self, mv: &ChessMove, _board: &Board) -> bool {
745 !self.is_move_attacking(mv, _board) }
748
749 fn evaluate_move_initiative(&self, board: &Board, mv: &ChessMove) -> f32 {
750 let mut initiative_value = 0.0;
751
752 if let Some(captured_piece) = board.piece_on(mv.get_dest()) {
754 initiative_value += match captured_piece {
755 Piece::Queen => 90.0,
756 Piece::Rook => 50.0,
757 Piece::Bishop | Piece::Knight => 30.0,
758 Piece::Pawn => 10.0,
759 Piece::King => 1000.0, };
761 }
762
763 let dest_centrality = self.square_centrality(mv.get_dest());
765 initiative_value += dest_centrality * 5.0;
766
767 let temp_board = board.make_move_new(*mv);
769 if temp_board.checkers().0 != 0 {
770 initiative_value += 25.0;
771 }
772
773 if let Some(enemy_king) = self.get_enemy_king_square(board) {
775 let king_area = self.get_king_area_squares(enemy_king);
776 if let Some(piece) = board.piece_on(mv.get_source()) {
777 for &area_square in &king_area {
778 if self.can_piece_attack_square(&temp_board, mv.get_dest(), piece, area_square)
779 {
780 initiative_value += 15.0;
781 break;
782 }
783 }
784 }
785 }
786
787 initiative_value
788 }
789
790 fn evaluate_move_positional(&self, board: &Board, mv: &ChessMove) -> f32 {
791 let mut positional_value = 0.0;
792
793 if let Some(piece) = board.piece_on(mv.get_source()) {
795 match piece {
796 Piece::Knight | Piece::Bishop => {
797 if self.is_piece_developed(mv.get_dest(), board.side_to_move()) {
799 positional_value += 15.0; }
801
802 if self.is_edge_square(mv.get_dest()) {
804 positional_value -= 10.0;
805 }
806
807 if piece == Piece::Bishop && self.is_long_diagonal(mv.get_dest()) {
809 positional_value += 12.0;
810 }
811 }
812 Piece::Pawn => {
813 if self.is_central_square(mv.get_dest()) {
815 positional_value += 8.0;
816 }
817
818 if self.creates_pawn_chain(board, mv) {
820 positional_value += 10.0;
821 }
822 }
823 Piece::Rook => {
824 if self.is_open_file(board, mv.get_dest()) {
826 positional_value += 15.0;
827 }
828
829 if self.is_seventh_rank(mv.get_dest(), board.side_to_move()) {
831 positional_value += 20.0;
832 }
833 }
834 Piece::Queen => {
835 if !self.is_early_game(board) {
837 let centrality = self.square_centrality(mv.get_dest());
838 positional_value += centrality * 8.0;
839 } else {
840 positional_value -= 15.0;
842 }
843 }
844 _ => {}
845 }
846 }
847
848 let centrality = self.square_centrality(mv.get_dest());
850 positional_value += centrality * 5.0; let temp_board = board.make_move_new(*mv);
854 let supporters_after = self.count_piece_supporters(&temp_board, mv.get_dest());
855 positional_value += supporters_after * 4.0; if self.is_attacked_by_enemy_pawns(&temp_board, mv.get_dest()) {
859 positional_value -= 15.0;
860 }
861
862 if self.improves_worst_piece(board, mv) {
864 positional_value += 12.0;
865 }
866
867 positional_value
868 }
869
870 fn is_enemy_king_attackable(&self, board: &Board) -> bool {
871 if let Some(king_square) = self.get_enemy_king_square(board) {
872 self.count_pieces_attacking_king_area(board, king_square) > 0
873 } else {
874 false
875 }
876 }
877
878 fn evaluate_safety_concerns(&self, board: &Board) -> f32 {
881 let mut safety_penalty = 0.0;
882 let our_color = board.side_to_move();
883
884 for square in chess::ALL_SQUARES {
886 if board.color_on(square) == Some(our_color) {
887 if let Some(piece) = board.piece_on(square) {
888 if self.is_square_attacked_by_enemy(board, square) {
889 let defenders = self.count_piece_supporters(board, square);
890 if defenders == 0.0 {
891 safety_penalty += self.piece_value(piece) * 0.8; } else if defenders < 1.0 {
894 safety_penalty += self.piece_value(piece) * 0.3; }
897 }
898 }
899 }
900 }
901
902 let king_square = board.king_square(our_color);
904 if self.is_square_attacked_by_enemy(board, king_square) {
905 safety_penalty += 200.0; }
907
908 let king_area = self.get_king_area_squares(king_square);
910 let mut attacked_king_squares = 0;
911 for &area_square in &king_area {
912 if self.is_square_attacked_by_enemy(board, area_square) {
913 attacked_king_squares += 1;
914 }
915 }
916 safety_penalty += attacked_king_squares as f32 * 15.0; safety_penalty
919 }
920
921 fn evaluate_move_safety(&self, board: &Board, mv: &ChessMove) -> f32 {
922 let mut safety_penalty = 0.0;
923 let our_color = board.side_to_move();
924 let enemy_color = !our_color;
925
926 let temp_board = board.make_move_new(*mv);
928 let moved_piece = board.piece_on(mv.get_source());
929
930 if let Some(piece) = moved_piece {
931 if self.is_square_attacked_by_color(&temp_board, mv.get_dest(), enemy_color) {
933 let defenders =
934 self.count_defenders_for_color(&temp_board, mv.get_dest(), our_color);
935 if defenders == 0.0 {
936 safety_penalty += self.piece_value(piece) * 1.2; } else {
939 let attackers =
941 self.count_attackers_for_color(&temp_board, mv.get_dest(), enemy_color);
942 if attackers > defenders && self.piece_value(piece) > 300.0 {
943 safety_penalty += self.piece_value(piece) * 0.7; }
945 }
946 }
947
948 if self.is_move_backward(mv, board.side_to_move())
950 && board.piece_on(mv.get_dest()).is_none()
951 {
952 safety_penalty += 25.0; }
954
955 if piece == Piece::Queen && self.is_early_game(board) {
957 let queen_attackers = self.count_enemy_attackers(&temp_board, mv.get_dest());
958 if queen_attackers > 0.0 {
959 safety_penalty += 150.0; }
961 }
962 }
963
964 let our_king_square = board.king_square(our_color);
966 if self.is_square_attacked_by_color(&temp_board, our_king_square, enemy_color)
967 && board.checkers().0 == 0
968 {
969 safety_penalty += 200.0; }
971
972 let hanging_penalty = self.count_hanging_pieces_after_move(&temp_board, our_color);
974 safety_penalty += hanging_penalty * 100.0; safety_penalty
977 }
978
979 fn piece_value(&self, piece: Piece) -> f32 {
980 match piece {
981 Piece::Pawn => 100.0,
982 Piece::Knight => 320.0,
983 Piece::Bishop => 330.0,
984 Piece::Rook => 500.0,
985 Piece::Queen => 900.0,
986 Piece::King => 10000.0, }
988 }
989
990 fn count_enemy_attackers(&self, board: &Board, square: Square) -> f32 {
991 let enemy_color = !board.side_to_move();
992 self.count_attackers_for_color(board, square, enemy_color)
993 }
994
995 fn is_move_backward(&self, mv: &ChessMove, color: chess::Color) -> bool {
996 let source_rank = mv.get_source().get_rank().to_index();
997 let dest_rank = mv.get_dest().get_rank().to_index();
998
999 match color {
1000 chess::Color::White => dest_rank < source_rank, chess::Color::Black => dest_rank > source_rank, }
1003 }
1004
1005 fn is_early_game(&self, board: &Board) -> bool {
1006 let mut developed_pieces = 0;
1008
1009 for square in chess::ALL_SQUARES {
1010 if let Some(piece) = board.piece_on(square) {
1011 if piece != Piece::Pawn && piece != Piece::King && self.is_piece_developed(square, board.color_on(square).unwrap()) {
1012 developed_pieces += 1;
1013 }
1014 }
1015 }
1016
1017 developed_pieces < 6 }
1019
1020 fn can_piece_attack_square(
1023 &self,
1024 board: &Board,
1025 piece_square: Square,
1026 _piece: Piece,
1027 target_square: Square,
1028 ) -> bool {
1029 let legal_moves: Vec<ChessMove> = MoveGen::new_legal(board)
1031 .filter(|mv| mv.get_source() == piece_square)
1032 .collect();
1033
1034 for mv in legal_moves {
1035 if mv.get_dest() == target_square {
1036 return true;
1037 }
1038 }
1039 false
1040 }
1041
1042 fn square_centrality(&self, square: Square) -> f32 {
1043 let file = square.get_file().to_index() as f32;
1045 let rank = square.get_rank().to_index() as f32;
1046
1047 let file_distance = (file - 3.5).abs();
1049 let rank_distance = (rank - 3.5).abs();
1050 let max_distance = file_distance.max(rank_distance);
1051
1052 (3.5 - max_distance) / 3.5
1054 }
1055
1056 fn is_piece_developed(&self, square: Square, color: chess::Color) -> bool {
1057 let rank = square.get_rank().to_index();
1059
1060 match color {
1061 chess::Color::White => rank > 1, chess::Color::Black => rank < 6, }
1064 }
1065
1066 fn is_square_attacked_by_enemy(&self, board: &Board, square: Square) -> bool {
1067 let enemy_color = !board.side_to_move();
1069 self.is_square_attacked_by_color(board, square, enemy_color)
1070 }
1071
1072 fn is_square_attacked_by_color(
1073 &self,
1074 board: &Board,
1075 square: Square,
1076 attacking_color: chess::Color,
1077 ) -> bool {
1078 for check_square in chess::ALL_SQUARES {
1080 if board.color_on(check_square) == Some(attacking_color) {
1081 if let Some(piece) = board.piece_on(check_square) {
1082 if self.can_piece_attack_square(board, check_square, piece, square) {
1083 return true;
1084 }
1085 }
1086 }
1087 }
1088 false
1089 }
1090
1091 fn count_defenders_for_color(
1092 &self,
1093 board: &Board,
1094 square: Square,
1095 defending_color: chess::Color,
1096 ) -> f32 {
1097 let mut defenders = 0.0;
1099
1100 for check_square in chess::ALL_SQUARES {
1101 if board.color_on(check_square) == Some(defending_color) && check_square != square {
1102 if let Some(piece) = board.piece_on(check_square) {
1103 if self.can_piece_attack_square(board, check_square, piece, square) {
1104 defenders += 1.0;
1105 }
1106 }
1107 }
1108 }
1109 defenders
1110 }
1111
1112 fn count_attackers_for_color(
1113 &self,
1114 board: &Board,
1115 square: Square,
1116 attacking_color: chess::Color,
1117 ) -> f32 {
1118 let mut attackers = 0.0;
1120
1121 for check_square in chess::ALL_SQUARES {
1122 if board.color_on(check_square) == Some(attacking_color) {
1123 if let Some(piece) = board.piece_on(check_square) {
1124 if self.can_piece_attack_square(board, check_square, piece, square) {
1125 attackers += 1.0;
1126 }
1127 }
1128 }
1129 }
1130 attackers
1131 }
1132
1133 fn count_hanging_pieces_after_move(&self, board: &Board, our_color: chess::Color) -> f32 {
1134 let mut hanging_pieces = 0.0;
1136 let enemy_color = !our_color;
1137
1138 for square in chess::ALL_SQUARES {
1139 if board.color_on(square) == Some(our_color) {
1140 if let Some(_piece) = board.piece_on(square) {
1141 if self.is_square_attacked_by_color(board, square, enemy_color) {
1142 let defenders = self.count_defenders_for_color(board, square, our_color);
1143 if defenders == 0.0 {
1144 hanging_pieces += 1.0; }
1146 }
1147 }
1148 }
1149 }
1150 hanging_pieces
1151 }
1152
1153 fn is_edge_square(&self, square: Square) -> bool {
1156 let file = square.get_file().to_index();
1157 let rank = square.get_rank().to_index();
1158 file == 0 || file == 7 || rank == 0 || rank == 7
1159 }
1160
1161 fn is_central_square(&self, square: Square) -> bool {
1162 let file = square.get_file().to_index();
1163 let rank = square.get_rank().to_index();
1164 (2..=5).contains(&file) && (2..=5).contains(&rank)
1165 }
1166
1167 fn is_long_diagonal(&self, square: Square) -> bool {
1168 let file = square.get_file().to_index();
1169 let rank = square.get_rank().to_index();
1170 (file == rank) || (file + rank == 7)
1172 }
1173
1174 fn creates_pawn_chain(&self, board: &Board, mv: &ChessMove) -> bool {
1175 if board.piece_on(mv.get_source()) != Some(Piece::Pawn) {
1176 return false;
1177 }
1178
1179 let dest = mv.get_dest();
1180 let our_color = board.side_to_move();
1181
1182 let supporting_squares = self.get_pawn_support_squares(dest, our_color);
1184 for support_square in supporting_squares {
1185 if board.piece_on(support_square) == Some(Piece::Pawn)
1186 && board.color_on(support_square) == Some(our_color)
1187 {
1188 return true;
1189 }
1190 }
1191 false
1192 }
1193
1194 fn get_pawn_support_squares(&self, square: Square, color: chess::Color) -> Vec<Square> {
1195 let mut support_squares = Vec::new();
1196 let file = square.get_file().to_index() as i8;
1197 let rank = square.get_rank().to_index() as i8;
1198
1199 let rank_offset = match color {
1200 chess::Color::White => -1, chess::Color::Black => 1, };
1203
1204 for file_offset in [-1, 1] {
1206 let support_file = file + file_offset;
1207 let support_rank = rank + rank_offset;
1208
1209 if (0..8).contains(&support_file) && (0..8).contains(&support_rank) {
1210 let support_square =
1211 unsafe { Square::new((support_rank * 8 + support_file) as u8) };
1212 support_squares.push(support_square);
1213 }
1214 }
1215
1216 support_squares
1217 }
1218
1219 fn is_open_file(&self, board: &Board, square: Square) -> bool {
1220 let file = square.get_file();
1221
1222 for rank_index in 0..8 {
1224 let check_square = unsafe { Square::new((rank_index * 8 + file.to_index()) as u8) };
1225 if board.piece_on(check_square) == Some(Piece::Pawn) {
1226 return false;
1227 }
1228 }
1229 true
1230 }
1231
1232 fn is_seventh_rank(&self, square: Square, color: chess::Color) -> bool {
1233 let rank = square.get_rank().to_index();
1234 match color {
1235 chess::Color::White => rank == 6, chess::Color::Black => rank == 1, }
1238 }
1239
1240 fn is_attacked_by_enemy_pawns(&self, board: &Board, square: Square) -> bool {
1241 let enemy_color = !board.side_to_move();
1242 let file = square.get_file().to_index() as i8;
1243 let rank = square.get_rank().to_index() as i8;
1244
1245 let pawn_attack_rank = match enemy_color {
1246 chess::Color::White => rank + 1, chess::Color::Black => rank - 1, };
1249
1250 if !(0..=7).contains(&pawn_attack_rank) {
1251 return false;
1252 }
1253
1254 for file_offset in [-1, 1] {
1256 let attack_file = file + file_offset;
1257 if (0..8).contains(&attack_file) {
1258 let attack_square =
1259 unsafe { Square::new((pawn_attack_rank * 8 + attack_file) as u8) };
1260 if board.piece_on(attack_square) == Some(Piece::Pawn)
1261 && board.color_on(attack_square) == Some(enemy_color)
1262 {
1263 return true;
1264 }
1265 }
1266 }
1267 false
1268 }
1269
1270 fn improves_worst_piece(&self, board: &Board, mv: &ChessMove) -> bool {
1271 let piece = board.piece_on(mv.get_source());
1272 if piece.is_none() {
1273 return false;
1274 }
1275
1276 let source_rank = mv.get_source().get_rank().to_index();
1278 let dest_rank = mv.get_dest().get_rank().to_index();
1279 let source_centrality = self.square_centrality(mv.get_source());
1280 let dest_centrality = self.square_centrality(mv.get_dest());
1281
1282 dest_centrality > source_centrality
1284 || (board.side_to_move() == chess::Color::White && dest_rank > source_rank)
1285 || (board.side_to_move() == chess::Color::Black && dest_rank < source_rank)
1286 }
1287}
1288
1289#[cfg(test)]
1290mod tests {
1291 use super::*;
1292 use chess::Board;
1293
1294 #[test]
1295 fn test_strategic_evaluator_creation() {
1296 let evaluator = StrategicEvaluator::new_default();
1297 assert_eq!(evaluator.config.initiative_weight, 0.4);
1298 assert!(!evaluator.attacking_patterns.is_empty());
1299 assert!(!evaluator.positional_plans.is_empty());
1300 }
1301
1302 #[test]
1303 fn test_strategic_evaluation() {
1304 let evaluator = StrategicEvaluator::new_default();
1305 let board = Board::default();
1306
1307 let strategic_eval = evaluator.evaluate_strategic(&board);
1308
1309 assert!(
1311 strategic_eval.initiative_bonus >= -100.0 && strategic_eval.initiative_bonus <= 100.0
1312 );
1313 assert!(strategic_eval.attacking_bonus >= 0.0);
1314 assert!(strategic_eval.base_evaluation >= -10.0 && strategic_eval.base_evaluation <= 10.0);
1316 }
1317
1318 #[test]
1319 fn test_proactive_move_generation() {
1320 let evaluator = StrategicEvaluator::aggressive();
1321 let board = Board::default();
1322
1323 let proactive_moves = evaluator.generate_proactive_moves(&board);
1324
1325 assert!(!proactive_moves.is_empty());
1326 assert!(proactive_moves.len() <= 40); }
1328
1329 #[test]
1330 fn test_aggressive_vs_balanced_config() {
1331 let aggressive = StrategicEvaluator::aggressive();
1332 let balanced = StrategicEvaluator::new_default();
1333
1334 assert!(aggressive.config.initiative_weight > balanced.config.initiative_weight);
1335 assert!(aggressive.config.attacking_piece_bonus > balanced.config.attacking_piece_bonus);
1336 }
1337
1338 #[test]
1339 fn test_strategic_patterns() {
1340 let evaluator = StrategicEvaluator::new_default();
1341
1342 assert!(evaluator
1343 .attacking_patterns
1344 .contains_key("king_attack_setup"));
1345 assert!(evaluator
1346 .attacking_patterns
1347 .contains_key("central_pressure"));
1348 assert!(evaluator
1349 .attacking_patterns
1350 .contains_key("piece_coordination"));
1351 }
1352
1353 #[test]
1354 fn test_hybrid_integration() {
1355 let evaluator = StrategicEvaluator::new_default();
1356 let board = Board::default();
1357
1358 let blended_eval = evaluator.blend_with_hybrid_evaluation(&board, 0.5, 0.3);
1359
1360 assert!(blended_eval.abs() < 100.0); }
1362
1363 #[test]
1364 fn test_initiative_evaluation() {
1365 let evaluator = StrategicEvaluator::aggressive();
1366 let board = Board::default();
1367
1368 let should_attack = evaluator.should_play_aggressively(&board);
1369 assert!(should_attack == true || should_attack == false);
1372 }
1373}