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)
729 && self.can_piece_attack_square(&temp_board, dest, piece, enemy_square)
730 {
731 return true;
732 }
733 }
734 }
735
736 let forward_progress = if board.side_to_move() == chess::Color::White {
738 dest.get_rank().to_index() > mv.get_source().get_rank().to_index()
739 } else {
740 dest.get_rank().to_index() < mv.get_source().get_rank().to_index()
741 };
742
743 forward_progress && dest.get_rank().to_index() > 3 && dest.get_rank().to_index() < 4
744 }
745
746 fn is_move_positional(&self, mv: &ChessMove, _board: &Board) -> bool {
747 !self.is_move_attacking(mv, _board) }
750
751 fn evaluate_move_initiative(&self, board: &Board, mv: &ChessMove) -> f32 {
752 let mut initiative_value = 0.0;
753
754 if let Some(captured_piece) = board.piece_on(mv.get_dest()) {
756 initiative_value += match captured_piece {
757 Piece::Queen => 90.0,
758 Piece::Rook => 50.0,
759 Piece::Bishop | Piece::Knight => 30.0,
760 Piece::Pawn => 10.0,
761 Piece::King => 1000.0, };
763 }
764
765 let dest_centrality = self.square_centrality(mv.get_dest());
767 initiative_value += dest_centrality * 5.0;
768
769 let temp_board = board.make_move_new(*mv);
771 if temp_board.checkers().0 != 0 {
772 initiative_value += 25.0;
773 }
774
775 if let Some(enemy_king) = self.get_enemy_king_square(board) {
777 let king_area = self.get_king_area_squares(enemy_king);
778 if let Some(piece) = board.piece_on(mv.get_source()) {
779 for &area_square in &king_area {
780 if self.can_piece_attack_square(&temp_board, mv.get_dest(), piece, area_square)
781 {
782 initiative_value += 15.0;
783 break;
784 }
785 }
786 }
787 }
788
789 initiative_value
790 }
791
792 fn evaluate_move_positional(&self, board: &Board, mv: &ChessMove) -> f32 {
793 let mut positional_value = 0.0;
794
795 if let Some(piece) = board.piece_on(mv.get_source()) {
797 match piece {
798 Piece::Knight | Piece::Bishop => {
799 if self.is_piece_developed(mv.get_dest(), board.side_to_move()) {
801 positional_value += 15.0; }
803
804 if self.is_edge_square(mv.get_dest()) {
806 positional_value -= 10.0;
807 }
808
809 if piece == Piece::Bishop && self.is_long_diagonal(mv.get_dest()) {
811 positional_value += 12.0;
812 }
813 }
814 Piece::Pawn => {
815 if self.is_central_square(mv.get_dest()) {
817 positional_value += 8.0;
818 }
819
820 if self.creates_pawn_chain(board, mv) {
822 positional_value += 10.0;
823 }
824 }
825 Piece::Rook => {
826 if self.is_open_file(board, mv.get_dest()) {
828 positional_value += 15.0;
829 }
830
831 if self.is_seventh_rank(mv.get_dest(), board.side_to_move()) {
833 positional_value += 20.0;
834 }
835 }
836 Piece::Queen => {
837 if !self.is_early_game(board) {
839 let centrality = self.square_centrality(mv.get_dest());
840 positional_value += centrality * 8.0;
841 } else {
842 positional_value -= 15.0;
844 }
845 }
846 _ => {}
847 }
848 }
849
850 let centrality = self.square_centrality(mv.get_dest());
852 positional_value += centrality * 5.0; let temp_board = board.make_move_new(*mv);
856 let supporters_after = self.count_piece_supporters(&temp_board, mv.get_dest());
857 positional_value += supporters_after * 4.0; if self.is_attacked_by_enemy_pawns(&temp_board, mv.get_dest()) {
861 positional_value -= 15.0;
862 }
863
864 if self.improves_worst_piece(board, mv) {
866 positional_value += 12.0;
867 }
868
869 positional_value
870 }
871
872 fn is_enemy_king_attackable(&self, board: &Board) -> bool {
873 if let Some(king_square) = self.get_enemy_king_square(board) {
874 self.count_pieces_attacking_king_area(board, king_square) > 0
875 } else {
876 false
877 }
878 }
879
880 fn evaluate_safety_concerns(&self, board: &Board) -> f32 {
883 let mut safety_penalty = 0.0;
884 let our_color = board.side_to_move();
885
886 for square in chess::ALL_SQUARES {
888 if board.color_on(square) == Some(our_color) {
889 if let Some(piece) = board.piece_on(square) {
890 if self.is_square_attacked_by_enemy(board, square) {
891 let defenders = self.count_piece_supporters(board, square);
892 if defenders == 0.0 {
893 safety_penalty += self.piece_value(piece) * 0.8; } else if defenders < 1.0 {
896 safety_penalty += self.piece_value(piece) * 0.3; }
899 }
900 }
901 }
902 }
903
904 let king_square = board.king_square(our_color);
906 if self.is_square_attacked_by_enemy(board, king_square) {
907 safety_penalty += 200.0; }
909
910 let king_area = self.get_king_area_squares(king_square);
912 let mut attacked_king_squares = 0;
913 for &area_square in &king_area {
914 if self.is_square_attacked_by_enemy(board, area_square) {
915 attacked_king_squares += 1;
916 }
917 }
918 safety_penalty += attacked_king_squares as f32 * 15.0; safety_penalty
921 }
922
923 fn evaluate_move_safety(&self, board: &Board, mv: &ChessMove) -> f32 {
924 let mut safety_penalty = 0.0;
925 let our_color = board.side_to_move();
926 let enemy_color = !our_color;
927
928 let temp_board = board.make_move_new(*mv);
930 let moved_piece = board.piece_on(mv.get_source());
931
932 if let Some(piece) = moved_piece {
933 if self.is_square_attacked_by_color(&temp_board, mv.get_dest(), enemy_color) {
935 let defenders =
936 self.count_defenders_for_color(&temp_board, mv.get_dest(), our_color);
937 if defenders == 0.0 {
938 safety_penalty += self.piece_value(piece) * 1.2; } else {
941 let attackers =
943 self.count_attackers_for_color(&temp_board, mv.get_dest(), enemy_color);
944 if attackers > defenders && self.piece_value(piece) > 300.0 {
945 safety_penalty += self.piece_value(piece) * 0.7; }
947 }
948 }
949
950 if self.is_move_backward(mv, board.side_to_move())
952 && board.piece_on(mv.get_dest()).is_none()
953 {
954 safety_penalty += 25.0; }
956
957 if piece == Piece::Queen && self.is_early_game(board) {
959 let queen_attackers = self.count_enemy_attackers(&temp_board, mv.get_dest());
960 if queen_attackers > 0.0 {
961 safety_penalty += 150.0; }
963 }
964 }
965
966 let our_king_square = board.king_square(our_color);
968 if self.is_square_attacked_by_color(&temp_board, our_king_square, enemy_color)
969 && board.checkers().0 == 0
970 {
971 safety_penalty += 200.0; }
973
974 let hanging_penalty = self.count_hanging_pieces_after_move(&temp_board, our_color);
976 safety_penalty += hanging_penalty * 100.0; safety_penalty
979 }
980
981 fn piece_value(&self, piece: Piece) -> f32 {
982 match piece {
983 Piece::Pawn => 100.0,
984 Piece::Knight => 320.0,
985 Piece::Bishop => 330.0,
986 Piece::Rook => 500.0,
987 Piece::Queen => 900.0,
988 Piece::King => 10000.0, }
990 }
991
992 fn count_enemy_attackers(&self, board: &Board, square: Square) -> f32 {
993 let enemy_color = !board.side_to_move();
994 self.count_attackers_for_color(board, square, enemy_color)
995 }
996
997 fn is_move_backward(&self, mv: &ChessMove, color: chess::Color) -> bool {
998 let source_rank = mv.get_source().get_rank().to_index();
999 let dest_rank = mv.get_dest().get_rank().to_index();
1000
1001 match color {
1002 chess::Color::White => dest_rank < source_rank, chess::Color::Black => dest_rank > source_rank, }
1005 }
1006
1007 fn is_early_game(&self, board: &Board) -> bool {
1008 let mut developed_pieces = 0;
1010
1011 for square in chess::ALL_SQUARES {
1012 if let Some(piece) = board.piece_on(square) {
1013 if piece != Piece::Pawn
1014 && piece != Piece::King
1015 && self.is_piece_developed(square, board.color_on(square).unwrap())
1016 {
1017 developed_pieces += 1;
1018 }
1019 }
1020 }
1021
1022 developed_pieces < 6 }
1024
1025 fn can_piece_attack_square(
1028 &self,
1029 board: &Board,
1030 piece_square: Square,
1031 _piece: Piece,
1032 target_square: Square,
1033 ) -> bool {
1034 let legal_moves: Vec<ChessMove> = MoveGen::new_legal(board)
1036 .filter(|mv| mv.get_source() == piece_square)
1037 .collect();
1038
1039 for mv in legal_moves {
1040 if mv.get_dest() == target_square {
1041 return true;
1042 }
1043 }
1044 false
1045 }
1046
1047 fn square_centrality(&self, square: Square) -> f32 {
1048 let file = square.get_file().to_index() as f32;
1050 let rank = square.get_rank().to_index() as f32;
1051
1052 let file_distance = (file - 3.5).abs();
1054 let rank_distance = (rank - 3.5).abs();
1055 let max_distance = file_distance.max(rank_distance);
1056
1057 (3.5 - max_distance) / 3.5
1059 }
1060
1061 fn is_piece_developed(&self, square: Square, color: chess::Color) -> bool {
1062 let rank = square.get_rank().to_index();
1064
1065 match color {
1066 chess::Color::White => rank > 1, chess::Color::Black => rank < 6, }
1069 }
1070
1071 fn is_square_attacked_by_enemy(&self, board: &Board, square: Square) -> bool {
1072 let enemy_color = !board.side_to_move();
1074 self.is_square_attacked_by_color(board, square, enemy_color)
1075 }
1076
1077 fn is_square_attacked_by_color(
1078 &self,
1079 board: &Board,
1080 square: Square,
1081 attacking_color: chess::Color,
1082 ) -> bool {
1083 for check_square in chess::ALL_SQUARES {
1085 if board.color_on(check_square) == Some(attacking_color) {
1086 if let Some(piece) = board.piece_on(check_square) {
1087 if self.can_piece_attack_square(board, check_square, piece, square) {
1088 return true;
1089 }
1090 }
1091 }
1092 }
1093 false
1094 }
1095
1096 fn count_defenders_for_color(
1097 &self,
1098 board: &Board,
1099 square: Square,
1100 defending_color: chess::Color,
1101 ) -> f32 {
1102 let mut defenders = 0.0;
1104
1105 for check_square in chess::ALL_SQUARES {
1106 if board.color_on(check_square) == Some(defending_color) && check_square != square {
1107 if let Some(piece) = board.piece_on(check_square) {
1108 if self.can_piece_attack_square(board, check_square, piece, square) {
1109 defenders += 1.0;
1110 }
1111 }
1112 }
1113 }
1114 defenders
1115 }
1116
1117 fn count_attackers_for_color(
1118 &self,
1119 board: &Board,
1120 square: Square,
1121 attacking_color: chess::Color,
1122 ) -> f32 {
1123 let mut attackers = 0.0;
1125
1126 for check_square in chess::ALL_SQUARES {
1127 if board.color_on(check_square) == Some(attacking_color) {
1128 if let Some(piece) = board.piece_on(check_square) {
1129 if self.can_piece_attack_square(board, check_square, piece, square) {
1130 attackers += 1.0;
1131 }
1132 }
1133 }
1134 }
1135 attackers
1136 }
1137
1138 fn count_hanging_pieces_after_move(&self, board: &Board, our_color: chess::Color) -> f32 {
1139 let mut hanging_pieces = 0.0;
1141 let enemy_color = !our_color;
1142
1143 for square in chess::ALL_SQUARES {
1144 if board.color_on(square) == Some(our_color) {
1145 if let Some(_piece) = board.piece_on(square) {
1146 if self.is_square_attacked_by_color(board, square, enemy_color) {
1147 let defenders = self.count_defenders_for_color(board, square, our_color);
1148 if defenders == 0.0 {
1149 hanging_pieces += 1.0; }
1151 }
1152 }
1153 }
1154 }
1155 hanging_pieces
1156 }
1157
1158 fn is_edge_square(&self, square: Square) -> bool {
1161 let file = square.get_file().to_index();
1162 let rank = square.get_rank().to_index();
1163 file == 0 || file == 7 || rank == 0 || rank == 7
1164 }
1165
1166 fn is_central_square(&self, square: Square) -> bool {
1167 let file = square.get_file().to_index();
1168 let rank = square.get_rank().to_index();
1169 (2..=5).contains(&file) && (2..=5).contains(&rank)
1170 }
1171
1172 fn is_long_diagonal(&self, square: Square) -> bool {
1173 let file = square.get_file().to_index();
1174 let rank = square.get_rank().to_index();
1175 (file == rank) || (file + rank == 7)
1177 }
1178
1179 fn creates_pawn_chain(&self, board: &Board, mv: &ChessMove) -> bool {
1180 if board.piece_on(mv.get_source()) != Some(Piece::Pawn) {
1181 return false;
1182 }
1183
1184 let dest = mv.get_dest();
1185 let our_color = board.side_to_move();
1186
1187 let supporting_squares = self.get_pawn_support_squares(dest, our_color);
1189 for support_square in supporting_squares {
1190 if board.piece_on(support_square) == Some(Piece::Pawn)
1191 && board.color_on(support_square) == Some(our_color)
1192 {
1193 return true;
1194 }
1195 }
1196 false
1197 }
1198
1199 fn get_pawn_support_squares(&self, square: Square, color: chess::Color) -> Vec<Square> {
1200 let mut support_squares = Vec::new();
1201 let file = square.get_file().to_index() as i8;
1202 let rank = square.get_rank().to_index() as i8;
1203
1204 let rank_offset = match color {
1205 chess::Color::White => -1, chess::Color::Black => 1, };
1208
1209 for file_offset in [-1, 1] {
1211 let support_file = file + file_offset;
1212 let support_rank = rank + rank_offset;
1213
1214 if (0..8).contains(&support_file) && (0..8).contains(&support_rank) {
1215 let support_square =
1216 unsafe { Square::new((support_rank * 8 + support_file) as u8) };
1217 support_squares.push(support_square);
1218 }
1219 }
1220
1221 support_squares
1222 }
1223
1224 fn is_open_file(&self, board: &Board, square: Square) -> bool {
1225 let file = square.get_file();
1226
1227 for rank_index in 0..8 {
1229 let check_square = unsafe { Square::new((rank_index * 8 + file.to_index()) as u8) };
1230 if board.piece_on(check_square) == Some(Piece::Pawn) {
1231 return false;
1232 }
1233 }
1234 true
1235 }
1236
1237 fn is_seventh_rank(&self, square: Square, color: chess::Color) -> bool {
1238 let rank = square.get_rank().to_index();
1239 match color {
1240 chess::Color::White => rank == 6, chess::Color::Black => rank == 1, }
1243 }
1244
1245 fn is_attacked_by_enemy_pawns(&self, board: &Board, square: Square) -> bool {
1246 let enemy_color = !board.side_to_move();
1247 let file = square.get_file().to_index() as i8;
1248 let rank = square.get_rank().to_index() as i8;
1249
1250 let pawn_attack_rank = match enemy_color {
1251 chess::Color::White => rank + 1, chess::Color::Black => rank - 1, };
1254
1255 if !(0..=7).contains(&pawn_attack_rank) {
1256 return false;
1257 }
1258
1259 for file_offset in [-1, 1] {
1261 let attack_file = file + file_offset;
1262 if (0..8).contains(&attack_file) {
1263 let attack_square =
1264 unsafe { Square::new((pawn_attack_rank * 8 + attack_file) as u8) };
1265 if board.piece_on(attack_square) == Some(Piece::Pawn)
1266 && board.color_on(attack_square) == Some(enemy_color)
1267 {
1268 return true;
1269 }
1270 }
1271 }
1272 false
1273 }
1274
1275 fn improves_worst_piece(&self, board: &Board, mv: &ChessMove) -> bool {
1276 let piece = board.piece_on(mv.get_source());
1277 if piece.is_none() {
1278 return false;
1279 }
1280
1281 let source_rank = mv.get_source().get_rank().to_index();
1283 let dest_rank = mv.get_dest().get_rank().to_index();
1284 let source_centrality = self.square_centrality(mv.get_source());
1285 let dest_centrality = self.square_centrality(mv.get_dest());
1286
1287 dest_centrality > source_centrality
1289 || (board.side_to_move() == chess::Color::White && dest_rank > source_rank)
1290 || (board.side_to_move() == chess::Color::Black && dest_rank < source_rank)
1291 }
1292}
1293
1294#[cfg(test)]
1295mod tests {
1296 use super::*;
1297 use chess::Board;
1298
1299 #[test]
1300 fn test_strategic_evaluator_creation() {
1301 let evaluator = StrategicEvaluator::new_default();
1302 assert_eq!(evaluator.config.initiative_weight, 0.4);
1303 assert!(!evaluator.attacking_patterns.is_empty());
1304 assert!(!evaluator.positional_plans.is_empty());
1305 }
1306
1307 #[test]
1308 fn test_strategic_evaluation() {
1309 let evaluator = StrategicEvaluator::new_default();
1310 let board = Board::default();
1311
1312 let strategic_eval = evaluator.evaluate_strategic(&board);
1313
1314 assert!(
1316 strategic_eval.initiative_bonus >= -100.0 && strategic_eval.initiative_bonus <= 100.0
1317 );
1318 assert!(strategic_eval.attacking_bonus >= 0.0);
1319 assert!(strategic_eval.base_evaluation >= -10.0 && strategic_eval.base_evaluation <= 10.0);
1321 }
1322
1323 #[test]
1324 fn test_proactive_move_generation() {
1325 let evaluator = StrategicEvaluator::aggressive();
1326 let board = Board::default();
1327
1328 let proactive_moves = evaluator.generate_proactive_moves(&board);
1329
1330 assert!(!proactive_moves.is_empty());
1331 assert!(proactive_moves.len() <= 40); }
1333
1334 #[test]
1335 fn test_aggressive_vs_balanced_config() {
1336 let aggressive = StrategicEvaluator::aggressive();
1337 let balanced = StrategicEvaluator::new_default();
1338
1339 assert!(aggressive.config.initiative_weight > balanced.config.initiative_weight);
1340 assert!(aggressive.config.attacking_piece_bonus > balanced.config.attacking_piece_bonus);
1341 }
1342
1343 #[test]
1344 fn test_strategic_patterns() {
1345 let evaluator = StrategicEvaluator::new_default();
1346
1347 assert!(evaluator
1348 .attacking_patterns
1349 .contains_key("king_attack_setup"));
1350 assert!(evaluator
1351 .attacking_patterns
1352 .contains_key("central_pressure"));
1353 assert!(evaluator
1354 .attacking_patterns
1355 .contains_key("piece_coordination"));
1356 }
1357
1358 #[test]
1359 fn test_hybrid_integration() {
1360 let evaluator = StrategicEvaluator::new_default();
1361 let board = Board::default();
1362
1363 let blended_eval = evaluator.blend_with_hybrid_evaluation(&board, 0.5, 0.3);
1364
1365 assert!(blended_eval.abs() < 100.0); }
1367
1368 #[test]
1369 fn test_initiative_evaluation() {
1370 let evaluator = StrategicEvaluator::aggressive();
1371 let board = Board::default();
1372
1373 let should_attack = evaluator.should_play_aggressively(&board);
1374 assert!(should_attack == true || should_attack == false);
1377 }
1378}