chess_vector_engine/
strategic_evaluator.rs

1use chess::{Board, ChessMove, File, MoveGen, Piece, Rank, Square};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5/// Strategic evaluation system for proactive, initiative-based play
6/// This module transforms Chess Vector Engine from reactive to proactive by:
7/// 1. Evaluating initiative and attacking potential
8/// 2. Generating strategic plans based on position patterns
9/// 3. Coordinating pieces toward strategic goals
10/// 4. Maintaining our hybrid approach (NNUE + Pattern Recognition + Strategic Planning)
11#[derive(Debug, Clone)]
12pub struct StrategicEvaluator {
13    config: StrategicConfig,
14    attacking_patterns: HashMap<String, AttackingPattern>,
15    positional_plans: Vec<PositionalPlan>,
16}
17
18/// Configuration for strategic evaluation emphasizing proactive play
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct StrategicConfig {
21    /// Weight for initiative and attacking chances vs positional safety (0.0-1.0)
22    pub initiative_weight: f32,
23
24    /// Bonus for pieces actively participating in attack (centipawns)
25    pub attacking_piece_bonus: f32,
26
27    /// Weight for dynamic factors like tempo, development, piece activity (0.0-1.0)
28    pub dynamic_factor_weight: f32,
29
30    /// Enable advanced pawn structure planning and pawn breaks
31    pub pawn_structure_planning: bool,
32
33    /// Bonus for controlling key squares around enemy king (centipawns)
34    pub king_attack_square_bonus: f32,
35
36    /// Weight for piece coordination in attacks (0.0-1.0)
37    pub piece_coordination_weight: f32,
38
39    /// Enable generation of forcing moves that create imbalances
40    pub enable_imbalance_creation: bool,
41}
42
43impl Default for StrategicConfig {
44    fn default() -> Self {
45        Self {
46            initiative_weight: 0.4,           // Balanced initiative vs safety
47            attacking_piece_bonus: 10.0,      // 10cp bonus per attacking piece (reduced)
48            dynamic_factor_weight: 0.3,       // Moderate tempo focus
49            pawn_structure_planning: true,    // Plan pawn breaks
50            king_attack_square_bonus: 8.0,    // 8cp per controlled attack square (reduced)
51            piece_coordination_weight: 0.4,   // Moderate coordination
52            enable_imbalance_creation: false, // Avoid unnecessary complications (changed)
53        }
54    }
55}
56
57impl StrategicConfig {
58    /// Aggressive configuration for maximum initiative and attacking play
59    pub fn aggressive() -> Self {
60        Self {
61            initiative_weight: 0.8,         // Heavy focus on initiative
62            attacking_piece_bonus: 25.0,    // High reward for attacking pieces
63            dynamic_factor_weight: 0.6,     // Very high tempo focus
64            king_attack_square_bonus: 15.0, // High reward for king attack
65            piece_coordination_weight: 0.7, // Strong piece coordination
66            enable_imbalance_creation: true,
67            ..Default::default()
68        }
69    }
70
71    /// Balanced configuration for strategic play with safety considerations
72    pub fn balanced() -> Self {
73        Self {
74            initiative_weight: 0.3,           // Safety-first balanced approach
75            attacking_piece_bonus: 8.0,       // Conservative attacking bonus
76            dynamic_factor_weight: 0.25,      // Moderate tempo consideration
77            king_attack_square_bonus: 6.0,    // Conservative king attack bonus
78            piece_coordination_weight: 0.5,   // Good coordination for safety
79            enable_imbalance_creation: false, // Avoid risky complications
80            ..Default::default()
81        }
82    }
83
84    /// Positional configuration focusing on safety and long-term advantages
85    pub fn positional() -> Self {
86        Self {
87            initiative_weight: 0.2,           // Very low initiative focus
88            attacking_piece_bonus: 5.0,       // Minimal attacking bonus
89            dynamic_factor_weight: 0.15,      // Focus on positional factors
90            pawn_structure_planning: true,    // Strong pawn planning
91            king_attack_square_bonus: 3.0,    // Minimal king attack focus
92            piece_coordination_weight: 0.7,   // High coordination for safety
93            enable_imbalance_creation: false, // Avoid complications completely
94        }
95    }
96
97    /// Safety-first configuration for solid, sound play
98    pub fn safety_first() -> Self {
99        Self {
100            initiative_weight: 0.15,          // Minimal initiative focus
101            attacking_piece_bonus: 3.0,       // Very low attacking bonus
102            dynamic_factor_weight: 0.1,       // Minimal tempo focus
103            pawn_structure_planning: true,    // Sound pawn structure
104            king_attack_square_bonus: 2.0,    // Very low attack bonus
105            piece_coordination_weight: 0.8,   // High coordination for safety
106            enable_imbalance_creation: false, // No complications at all
107        }
108    }
109
110    /// Master-level configuration for 2000+ ELO play
111    pub fn master_level() -> Self {
112        Self {
113            initiative_weight: 0.25,          // Measured initiative
114            attacking_piece_bonus: 6.0,       // Controlled attacking
115            dynamic_factor_weight: 0.2,       // Good tempo sense
116            pawn_structure_planning: true,    // Essential for master play
117            king_attack_square_bonus: 5.0,    // Balanced king safety
118            piece_coordination_weight: 0.6,   // Strong coordination
119            enable_imbalance_creation: false, // Avoid unnecessary risks
120        }
121    }
122}
123
124/// Strategic patterns for recognizing attacking and positional motifs
125#[derive(Debug, Clone)]
126pub struct AttackingPattern {
127    pub name: String,
128    pub description: String,
129    pub initiative_bonus: f32, // Bonus for positions matching this pattern
130    pub required_pieces: Vec<Piece>, // Pieces that must be present
131    pub target_squares: Vec<Square>, // Key squares this pattern targets
132    pub coordination_bonus: f32, // Bonus when pieces coordinate in this pattern
133}
134
135/// Strategic plans for proactive play
136#[derive(Debug, Clone)]
137pub struct PositionalPlan {
138    pub name: String,
139    pub goal: PlanGoal,
140    pub required_moves: Vec<ChessMove>, // Moves that advance this plan
141    pub evaluation_bonus: f32,          // Bonus for positions that advance this plan
142    pub urgency: PlanUrgency,           // How quickly this plan should be executed
143}
144
145#[derive(Debug, Clone, PartialEq)]
146pub enum PlanGoal {
147    AttackKing,        // Direct attack on enemy king
148    ControlCenter,     // Gain central control
149    CreateWeaknesses,  // Force weaknesses in enemy position
150    PieceCoordination, // Improve piece coordination
151    PawnStructure,     // Improve pawn structure or create breaks
152    InitiativeSeizure, // Seize the initiative with forcing moves
153}
154
155#[derive(Debug, Clone, PartialEq)]
156pub enum PlanUrgency {
157    Immediate, // Execute this plan now
158    High,      // Execute soon
159    Medium,    // Execute when opportunity arises
160    Low,       // Long-term positional goal
161}
162
163/// Result of strategic evaluation
164#[derive(Debug, Clone)]
165pub struct StrategicEvaluation {
166    pub base_evaluation: f32,                     // Base position evaluation
167    pub initiative_bonus: f32,                    // Bonus for initiative and attacking chances
168    pub attacking_bonus: f32,                     // Bonus for pieces participating in attack
169    pub coordination_bonus: f32,                  // Bonus for piece coordination
170    pub plan_bonus: f32,                          // Bonus for advancing strategic plans
171    pub total_evaluation: f32,                    // Final strategic evaluation
172    pub recommended_plan: Option<PositionalPlan>, // Suggested strategic plan
173    pub attacking_moves: Vec<ChessMove>,          // Moves that support attack
174    pub positional_moves: Vec<ChessMove>,         // Moves that improve position
175}
176
177impl StrategicEvaluator {
178    /// Create a new strategic evaluator with configuration
179    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    /// Create strategic evaluator with default balanced configuration
193    pub fn new_default() -> Self {
194        Self::new(StrategicConfig::default())
195    }
196
197    /// Create aggressive strategic evaluator for maximum initiative
198    pub fn aggressive() -> Self {
199        Self::new(StrategicConfig::aggressive())
200    }
201
202    /// Evaluate position strategically, focusing on initiative balanced with safety
203    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        // v0.4.0: Add safety penalty for reckless play
211        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; // Subtract safety concerns
219
220        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    /// Generate proactive moves that seize initiative while maintaining safety
237    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        // Prioritize attacking moves when we have initiative
242        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; // Balance initiative with safety
246
247            if final_value > 50.0 {
248                // Ultra-strict threshold for attacking moves (prevent blunders)
249                proactive_moves.push((mv, final_value));
250            }
251        }
252
253        // Add positional moves that improve our strategic position
254        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                // Ultra-strict threshold for positional moves (prevent blunders)
261                proactive_moves.push((mv, final_value));
262            }
263        }
264
265        // Sort by strategic value (highest first)
266        proactive_moves.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
267
268        // Limit to top 10 moves to avoid overwhelming with bad moves
269        proactive_moves.truncate(10);
270
271        proactive_moves
272    }
273
274    /// Check if position favors initiative and attacking play
275    pub fn should_play_aggressively(&self, board: &Board) -> bool {
276        let strategic_eval = self.evaluate_strategic(board);
277
278        // Play aggressively if:
279        // 1. We have initiative advantage
280        // 2. Good piece coordination for attack
281        // 3. Enemy king is not fully safe
282        strategic_eval.initiative_bonus > 0.0
283            && strategic_eval.coordination_bonus > 0.0
284            && self.is_enemy_king_attackable(board)
285    }
286
287    /// Integration with existing hybrid evaluation system
288    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        // Blend strategic evaluation with existing NNUE and pattern evaluations
297        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    // Private implementation methods
311
312    fn initialize_attacking_patterns(&mut self) {
313        // King attack patterns
314        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![], // Will be calculated based on enemy king position
322                coordination_bonus: 25.0,
323            },
324        );
325
326        // Central attack patterns
327        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        // Piece coordination patterns
340        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![], // Will be calculated dynamically
358            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        // Basic material and positional evaluation
381        // This integrates with our existing evaluation systems
382        let mut eval = 0.0;
383
384        // Material count with strategic adjustments
385        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 // Convert to centipawn-like scale
405    }
406
407    fn evaluate_initiative(&self, board: &Board) -> f32 {
408        let mut initiative = 0.0;
409
410        // More legal moves = more initiative
411        let our_moves = MoveGen::new_legal(board).count() as f32;
412
413        // Simulate opponent's response to estimate their mobility
414        let opponent_mobility = self.estimate_opponent_mobility(board);
415
416        // Initiative bonus based on mobility advantage
417        let mobility_advantage = our_moves - opponent_mobility;
418        initiative += mobility_advantage * 2.0;
419
420        // Bonus for having the move in sharp positions
421        if self.is_position_sharp(board) {
422            initiative += 20.0;
423        }
424
425        // Bonus for controlling key central squares
426        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            // Count pieces attacking enemy king area
437            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            // Bonus for controlling squares around enemy king
441            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        // Check for pieces supporting each other
453        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                // Only include attacking moves that don't hang pieces badly
487                let safety_penalty = self.evaluate_move_safety(board, &mv);
488                if safety_penalty < 50.0 {
489                    // Don't hang even 0.5 pawns worth (very strict)
490                    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                // Only include positional moves that are reasonably safe
504                let safety_penalty = self.evaluate_move_safety(board, &mv);
505                if safety_penalty < 30.0 {
506                    // Don't hang even 0.3 pawns worth (extremely strict)
507                    positional_moves.push(mv);
508                }
509            }
510        }
511
512        positional_moves
513    }
514
515    // Helper methods for strategic evaluation
516
517    fn estimate_opponent_mobility(&self, board: &Board) -> f32 {
518        // Estimate opponent's mobility by simulating their turn
519        let current_side = board.side_to_move();
520
521        // Note: We can't actually change side_to_move in chess crate, so we approximate
522
523        // Count legal moves for opponent pieces
524        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                    // Count possible destinations for this piece
531                    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        // Check if position has tactical opportunities
545        let mut sharp_indicators = 0;
546
547        // 1. Many legal moves (tactical complexity)
548        let legal_moves = MoveGen::new_legal(board).count();
549        if legal_moves > 35 {
550            sharp_indicators += 1;
551        }
552
553        // 2. Pieces under attack
554        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        // 3. King in danger
565        if board.checkers().0 != 0 {
566            sharp_indicators += 2; // Check is very sharp
567        }
568
569        // 4. Many captures available
570        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 // Position is sharp if multiple indicators present
578    }
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        // Count our pieces that can attack squares around enemy king
600        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        // Add squares around the king (3x3 area)
635        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        // Check if piece can attack any square in the king area
661        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        // Check if any of our pieces can attack this square
671        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        // Count pieces that can defend/support this piece
687        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        // Evaluate how feasible this plan is in current position
704        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        // Check if move is attacking in nature
714        let dest = mv.get_dest();
715
716        // 1. Captures are attacking
717        if board.piece_on(dest).is_some() {
718            return true;
719        }
720
721        // 2. Moves that attack enemy pieces
722        if let Some(piece) = board.piece_on(mv.get_source()) {
723            // Check if this move puts pressure on enemy pieces
724            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        // 3. Moves toward enemy territory
735        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        // Check if move improves position
746        !self.is_move_attacking(mv, _board) // Non-attacking moves are positional
747    }
748
749    fn evaluate_move_initiative(&self, board: &Board, mv: &ChessMove) -> f32 {
750        let mut initiative_value = 0.0;
751
752        // Higher value for captures
753        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, // Should never happen in legal moves
760            };
761        }
762
763        // Bonus for centralization
764        let dest_centrality = self.square_centrality(mv.get_dest());
765        initiative_value += dest_centrality * 5.0;
766
767        // Bonus for check moves
768        let temp_board = board.make_move_new(*mv);
769        if temp_board.checkers().0 != 0 {
770            initiative_value += 25.0;
771        }
772
773        // Bonus for attacking enemy king area
774        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        // Master-level positional principles
794        if let Some(piece) = board.piece_on(mv.get_source()) {
795            match piece {
796                Piece::Knight | Piece::Bishop => {
797                    // Development bonus - stronger for first development
798                    if self.is_piece_developed(mv.get_dest(), board.side_to_move()) {
799                        positional_value += 15.0; // Higher development bonus
800                    }
801
802                    // Knights on rim are dim - penalty for edge moves
803                    if self.is_edge_square(mv.get_dest()) {
804                        positional_value -= 10.0;
805                    }
806
807                    // Bishops on long diagonals
808                    if piece == Piece::Bishop && self.is_long_diagonal(mv.get_dest()) {
809                        positional_value += 12.0;
810                    }
811                }
812                Piece::Pawn => {
813                    // Advanced pawns in center
814                    if self.is_central_square(mv.get_dest()) {
815                        positional_value += 8.0;
816                    }
817
818                    // Pawn chains and support
819                    if self.creates_pawn_chain(board, mv) {
820                        positional_value += 10.0;
821                    }
822                }
823                Piece::Rook => {
824                    // Rooks on open files
825                    if self.is_open_file(board, mv.get_dest()) {
826                        positional_value += 15.0;
827                    }
828
829                    // Rooks on 7th rank
830                    if self.is_seventh_rank(mv.get_dest(), board.side_to_move()) {
831                        positional_value += 20.0;
832                    }
833                }
834                Piece::Queen => {
835                    // Queen centralization in middlegame
836                    if !self.is_early_game(board) {
837                        let centrality = self.square_centrality(mv.get_dest());
838                        positional_value += centrality * 8.0;
839                    } else {
840                        // Penalty for early queen moves
841                        positional_value -= 15.0;
842                    }
843                }
844                _ => {}
845            }
846        }
847
848        // Master-level positional factors
849        let centrality = self.square_centrality(mv.get_dest());
850        positional_value += centrality * 5.0; // Higher centrality bonus
851
852        // Piece coordination and harmony
853        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; // Higher coordination bonus
856
857        // Avoid squares attacked by enemy pawns (weakness principle)
858        if self.is_attacked_by_enemy_pawns(&temp_board, mv.get_dest()) {
859            positional_value -= 15.0;
860        }
861
862        // Improve worst-placed piece principle
863        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    // Safety evaluation functions to balance aggression with sound play
879
880    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        // Check for hanging pieces
885        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                            // Piece is hanging - major safety concern
892                            safety_penalty += self.piece_value(piece) * 0.8; // 80% of piece value
893                        } else if defenders < 1.0 {
894                            // Piece is under-defended
895                            safety_penalty += self.piece_value(piece) * 0.3; // 30% of piece value
896                        }
897                    }
898                }
899            }
900        }
901
902        // King safety penalty
903        let king_square = board.king_square(our_color);
904        if self.is_square_attacked_by_enemy(board, king_square) {
905            safety_penalty += 200.0; // King under attack is very dangerous
906        }
907
908        // Check if king area is under attack
909        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; // Each attacked square around king
917
918        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        // Simulate the move to check resulting position
927        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            // Check if the moved piece becomes hanging (check attacks by enemy on temp board)
932            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                    // Piece hangs after the move - critical safety issue
937                    safety_penalty += self.piece_value(piece) * 1.2; // 120% penalty to strongly discourage
938                } else {
939                    // Piece is attacked but defended - still risky for valuable pieces
940                    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; // 70% penalty for risky valuable piece moves
944                    }
945                }
946            }
947
948            // Penalty for moving pieces backward without good reason (anti-development)
949            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; // Discourage backward moves unless capturing
953            }
954
955            // Penalty for early queen moves that might get attacked
956            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; // Heavy penalty for exposing queen early
960                }
961            }
962        }
963
964        // Check if move exposes our king
965        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; // Heavy penalty for exposing king to check
970        }
971
972        // Additional check: Does this move leave other pieces hanging?
973        let hanging_penalty = self.count_hanging_pieces_after_move(&temp_board, our_color);
974        safety_penalty += hanging_penalty * 100.0; // 100cp per hanging piece
975
976        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, // King is invaluable
987        }
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, // White moving backwards
1001            chess::Color::Black => dest_rank > source_rank, // Black moving backwards
1002        }
1003    }
1004
1005    fn is_early_game(&self, board: &Board) -> bool {
1006        // Count developed pieces - if few pieces developed, it's early game
1007        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 // Early game if less than 6 pieces developed total
1018    }
1019
1020    // Helper functions for strategic evaluation
1021
1022    fn can_piece_attack_square(
1023        &self,
1024        board: &Board,
1025        piece_square: Square,
1026        _piece: Piece,
1027        target_square: Square,
1028    ) -> bool {
1029        // Generate legal moves for the piece and check if target is among them
1030        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        // Calculate how central a square is (higher values for central squares)
1044        let file = square.get_file().to_index() as f32;
1045        let rank = square.get_rank().to_index() as f32;
1046
1047        // Distance from center (files 3.5, ranks 3.5)
1048        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        // Return centrality (0.0 for corners, 1.0 for center)
1053        (3.5 - max_distance) / 3.5
1054    }
1055
1056    fn is_piece_developed(&self, square: Square, color: chess::Color) -> bool {
1057        // Check if piece is developed from starting position
1058        let rank = square.get_rank().to_index();
1059
1060        match color {
1061            chess::Color::White => rank > 1, // White pieces developed beyond 2nd rank
1062            chess::Color::Black => rank < 6, // Black pieces developed beyond 7th rank
1063        }
1064    }
1065
1066    fn is_square_attacked_by_enemy(&self, board: &Board, square: Square) -> bool {
1067        // Check if enemy pieces can attack this square
1068        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        // Check if pieces of specific color can attack this square
1079        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        // Count pieces of specific color that can defend this square
1098        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        // Count pieces of specific color that can attack this square
1119        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        // Count how many of our pieces are hanging after this position
1135        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; // Piece is hanging
1145                        }
1146                    }
1147                }
1148            }
1149        }
1150        hanging_pieces
1151    }
1152
1153    // Master-level positional evaluation helpers
1154
1155    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        // a1-h8 diagonal or h1-a8 diagonal
1171        (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        // Check if this pawn supports or is supported by other pawns
1183        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, // White pawns support from behind
1201            chess::Color::Black => 1,  // Black pawns support from behind
1202        };
1203
1204        // Check diagonally behind for supporting pawns
1205        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        // Check if there are no pawns on this file
1223        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, // 7th rank for White
1236            chess::Color::Black => rank == 1, // 2nd rank for Black (their 7th)
1237        }
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, // White pawns attack from below
1247            chess::Color::Black => rank - 1, // Black pawns attack from above
1248        };
1249
1250        if !(0..=7).contains(&pawn_attack_rank) {
1251            return false;
1252        }
1253
1254        // Check diagonal attacks from enemy pawns
1255        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        // Simplified check: moving from back rank or edge improves piece activity
1277        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        // Piece becomes more active (more central or advanced)
1283        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        // Strategic evaluation should be calculated - allow negative values for initiative
1310        assert!(
1311            strategic_eval.initiative_bonus >= -100.0 && strategic_eval.initiative_bonus <= 100.0
1312        );
1313        assert!(strategic_eval.attacking_bonus >= 0.0);
1314        // Base evaluation can be 0.0 for equal starting position
1315        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); // Reasonable number of moves
1327    }
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); // Reasonable evaluation range
1361    }
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        // Starting position might not favor immediate aggression
1370        // Test just ensures method runs without error
1371        assert!(should_attack == true || should_attack == false);
1372    }
1373}