diff --git a/store/src/game_rules_moves.rs b/store/src/game_rules_moves.rs index 1a67340..fa024f6 100644 --- a/store/src/game_rules_moves.rs +++ b/store/src/game_rules_moves.rs @@ -361,35 +361,175 @@ impl MoveRules { with_excedents: bool, ignored_rules: Vec, ) -> Vec<(CheckerMove, CheckerMove)> { - let (dice1, dice2) = self.dice.values; - let (dice_max, dice_min) = if dice1 > dice2 { - (dice1, dice2) - } else { - (dice2, dice1) - }; - let mut moves_seqs = self.get_possible_moves_sequences_by_dices( - dice_max, - dice_min, - with_excedents, - false, - ignored_rules.clone(), - ); - // if we got valid sequences with the highest die, we don't accept sequences using only the - // lowest die - let ignore_empty = !moves_seqs.is_empty(); - let mut moves_seqs_order2 = self.get_possible_moves_sequences_by_dices( - dice_min, - dice_max, - with_excedents, - ignore_empty, - ignored_rules, - ); - moves_seqs.append(&mut moves_seqs_order2); - let empty_removed = moves_seqs + // Etape 1: Générer tous les mouvements potentiels sans appliquer les règles complexes récursives. + let mut potential_moves = self.generate_all_potential_moves(with_excedents); + + // Etape 2: Appliquer les filtres pour les règles complexes de manière itérative. + + // Règle: MustFillQuarter + if !ignored_rules.contains(&TricTracRule::MustFillQuarter) { + let filling_moves: Vec<_> = potential_moves + .iter() + .filter(|moves| { + let mut board = self.board.clone(); + if board.move_checker(&Color::White, moves.0).is_ok() + && board.move_checker(&Color::White, moves.1).is_ok() + { + return board.any_quarter_filled(Color::White); + } + false + }) + .cloned() + .collect(); + + if !filling_moves.is_empty() { + potential_moves = filling_moves; + } + } + + // Règle: Règles de sortie (Exit) + if !ignored_rules.contains(&TricTracRule::Exit) { + // Filtrer les mouvements qui ne respectent pas les règles de base de sortie. + potential_moves.retain(|moves| self.check_exit_rules(moves).is_ok()); + + // Gérer ExitByEffectPossible: si un mouvement sans excédent est possible, les mouvements avec excédent sont interdits. + let non_excess_moves = self.generate_all_potential_moves(false); + if !non_excess_moves.is_empty() { + // Vérifier s'il existe des mouvements valides sans excédent + let has_valid_non_excess_move = non_excess_moves.iter().any(|moves| { + self.check_exit_rules(moves).is_ok() + && self.check_corner_rules(moves).is_ok() + && self.check_opponent_can_fill_quarter_rule(moves).is_ok() + }); + + if has_valid_non_excess_move { + potential_moves.retain(|moves| { + !self.is_exit_by_excess(moves, &non_excess_moves) + }); + } + } + } + + // Règle: MustPlayAllDice + let has_two_dice_move = potential_moves .iter() - .filter(|(c1, c2)| *c1 != EMPTY_MOVE && *c2 != EMPTY_MOVE); - if empty_removed.count() > 0 { - moves_seqs.retain(|(c1, c2)| *c1 != EMPTY_MOVE && *c2 != EMPTY_MOVE); + .any(|(m1, m2)| *m1 != EMPTY_MOVE && *m2 != EMPTY_MOVE); + if has_two_dice_move { + potential_moves.retain(|(m1, m2)| *m1 != EMPTY_MOVE && *m2 != EMPTY_MOVE); + } + + // Règle: MustPlayStrongerDie + if !has_two_dice_move && !potential_moves.is_empty() { + let (dice1, dice2) = self.dice.values; + if dice1 != dice2 { + let stronger_die = cmp::max(dice1, dice2); + let uses_stronger_die = potential_moves.iter().any(|(m1, _)| { + if m1.get_from() == 0 { + return false; + } + let dist = (m1.get_to() as i8 - m1.get_from() as i8).unsigned_abs(); + dist == stronger_die + }); + if uses_stronger_die { + potential_moves.retain(|(m1, _)| { + if m1.get_from() == 0 { + return false; + } + let dist = (m1.get_to() as i8 - m1.get_from() as i8).unsigned_abs(); + dist == stronger_die + }); + } + } + } + + if potential_moves.is_empty() && with_excedents { + return vec![(EMPTY_MOVE, EMPTY_MOVE)]; + } + + potential_moves + } + + /// Helper pour `get_possible_moves_sequences`. + /// Détermine si un mouvement est une sortie "par effet" (avec excédent) + /// en vérifiant s'il n'existe pas dans la liste des mouvements sans excédent. + fn is_exit_by_excess( + &self, + moves: &(CheckerMove, CheckerMove), + non_excess_moves: &[(CheckerMove, CheckerMove)], + ) -> bool { + if !moves.0.is_exit() && !moves.1.is_exit() { + return false; + } + !non_excess_moves.contains(moves) + } + + /// Nouvelle fonction itérative pour générer les mouvements bruts. + fn generate_all_potential_moves( + &self, + with_excedents: bool, + ) -> Vec<(CheckerMove, CheckerMove)> { + let mut moves_seqs = Vec::new(); + let color = &Color::White; + let forbid_exits = self.has_checkers_outside_last_quarter(); + let (dice1, dice2) = self.dice.values; + let dice_orders = if dice1 == dice2 { + vec![(dice1, dice2)] + } else { + vec![(dice1, dice2), (dice2, dice1)] + }; + + for (d1, d2) in dice_orders { + for first_move in + self.board + .get_possible_moves(*color, d1, with_excedents, false, forbid_exits) + { + let mut board2 = self.board.clone(); + if board2.move_checker(color, first_move).is_err() { + continue; + } + + let mut has_second_dice_move = false; + for second_move in + board2.get_possible_moves(*color, d2, with_excedents, true, forbid_exits) + { + let current_moves = (first_move, second_move); + + // Valider la séquence en la simulant sur un plateau vierge + let mut scratch_board = self.board.clone(); + if scratch_board.move_checker(color, current_moves.0).is_ok() + && scratch_board.move_checker(color, current_moves.1).is_ok() + { + // Si l'exécution est valide, appliquer les autres règles + if self.check_corner_rules(¤t_moves).is_ok() + && self + .check_opponent_can_fill_quarter_rule(¤t_moves) + .is_ok() + && !(self.is_move_by_puissance(¤t_moves) + && self.can_take_corner_by_effect()) + { + if !moves_seqs.contains(¤t_moves) { + moves_seqs.push(current_moves); + } + has_second_dice_move = true; + } + } + } + + if !has_second_dice_move { + let current_moves = (first_move, EMPTY_MOVE); + if self.check_corner_rules(¤t_moves).is_ok() + && self + .check_opponent_can_fill_quarter_rule(¤t_moves) + .is_ok() + && !(self.is_move_by_puissance(¤t_moves) + && self.can_take_corner_by_effect()) + { + if !moves_seqs.contains(¤t_moves) { + moves_seqs.push(current_moves); + } + } + } + } } moves_seqs } @@ -447,14 +587,16 @@ impl MoveRules { pub fn get_quarter_filling_moves_sequences(&self) -> Vec<(CheckerMove, CheckerMove)> { let mut moves_seqs = Vec::new(); let color = &Color::White; - let ignored_rules = vec![TricTracRule::Exit, TricTracRule::MustFillQuarter]; - for moves in self.get_possible_moves_sequences(true, ignored_rules) { + // Utilise la nouvelle méthode non-récursive + let potential_moves = self.generate_all_potential_moves(true); + + for moves in potential_moves { let mut board = self.board.clone(); - board.move_checker(color, moves.0).unwrap(); - board.move_checker(color, moves.1).unwrap(); - // println!("get_quarter_filling_moves_sequences board : {:?}", board); - if board.any_quarter_filled(*color) && !moves_seqs.contains(&moves) { - moves_seqs.push(moves); + // On ne peut pas juste unwrap, il faut gérer l'erreur + if board.move_checker(color, moves.0).is_ok() && board.move_checker(color, moves.1).is_ok() { + if board.any_quarter_filled(*color) && !moves_seqs.contains(&moves) { + moves_seqs.push(moves); + } } } moves_seqs @@ -468,64 +610,10 @@ impl MoveRules { ignore_empty: bool, ignored_rules: Vec, ) -> Vec<(CheckerMove, CheckerMove)> { - let mut moves_seqs = Vec::new(); - let color = &Color::White; - let forbid_exits = self.has_checkers_outside_last_quarter(); - for first_move in - self.board - .get_possible_moves(*color, dice1, with_excedents, false, forbid_exits) - { - let mut board2 = self.board.clone(); - if board2.move_checker(color, first_move).is_err() { - println!("err move"); - continue; - } - - // XXX : the goal here is to replicate moves_allowed() checks without using get_possible_moves_sequences to - // avoid an infinite loop... - let mut has_second_dice_move = false; - for second_move in - board2.get_possible_moves(*color, dice2, with_excedents, true, forbid_exits) - { - if self.check_corner_rules(&(first_move, second_move)).is_ok() - && self - .check_opponent_can_fill_quarter_rule(&(first_move, second_move)) - .is_ok() - && !(self.is_move_by_puissance(&(first_move, second_move)) - && self.can_take_corner_by_effect()) - && (ignored_rules.contains(&TricTracRule::Exit) - || self.check_exit_rules(&(first_move, second_move)).is_ok()) - && (ignored_rules.contains(&TricTracRule::MustFillQuarter) - || self - .check_must_fill_quarter_rule(&(first_move, second_move)) - .is_ok()) - { - moves_seqs.push((first_move, second_move)); - has_second_dice_move = true; - } - } - if !has_second_dice_move - && with_excedents - && !ignore_empty - && self.check_corner_rules(&(first_move, EMPTY_MOVE)).is_ok() - && self - .check_opponent_can_fill_quarter_rule(&(first_move, EMPTY_MOVE)) - .is_ok() - && !(self.is_move_by_puissance(&(first_move, EMPTY_MOVE)) - && self.can_take_corner_by_effect()) - && (ignored_rules.contains(&TricTracRule::Exit) - || self.check_exit_rules(&(first_move, EMPTY_MOVE)).is_ok()) - && (ignored_rules.contains(&TricTracRule::MustFillQuarter) - || self - .check_must_fill_quarter_rule(&(first_move, EMPTY_MOVE)) - .is_ok()) - { - // empty move - moves_seqs.push((first_move, EMPTY_MOVE)); - } - //if board2.get_color_fields(*color).is_empty() { - } - moves_seqs + // NOTE: Cette fonction est maintenant obsolète et remplacée par la logique dans get_possible_moves_sequences. + // On la garde pour la compatibilité mais elle devrait être enlevée à terme. + // Pour l'instant, on délègue à la nouvelle implémentation. + self.get_possible_moves_sequences(with_excedents, ignored_rules) } fn _get_direct_exit_moves(&self, state: &GameState) -> Vec {