diff --git a/store/src/board.rs b/store/src/board.rs index d077492..04b656e 100644 --- a/store/src/board.rs +++ b/store/src/board.rs @@ -446,13 +446,51 @@ impl Board { let fields = self.get_quarter_fields(field); !fields.iter().any(|field| { if color == Color::White { - self.positions[field - 1] < 1 + self.positions[field - 1] < 2 } else { - self.positions[field - 1] > -1 + self.positions[field - 1] > -2 } }) } + pub fn get_quarter_filling_candidate(&self, color: Color) -> Vec { + let mut missing = vec![]; + // first quarter + for quarter in [1..7, 7..13, 13..19, 19..25] { + missing = vec![]; + for field in quarter { + let field_count = if color == Color::Black { + 0 - self.positions[field - 1] + } else { + self.positions[field - 1] + }; + if field_count < 0 { + // opponent checker found : this quarter cannot be filled + missing = vec![]; + continue; + } + if field_count == 0 { + missing.push(field); + missing.push(field); + } else if field_count == 1 { + missing.push(field); + } + } + if missing.len() < 3 { + // fillable quarter found (no more than two missing checkers) + if let Some(field) = missing.first() { + // We check that there are sufficient checkers left to fill the quarter + if !self.is_quarter_fillable(color, *field) { + missing = vec![]; + } + } + // there will be no other fillable quarter + break; + } + } + missing + } + /// Returns whether the `color` player can still fill the quarter containing the `field` /// * `color` - color of the player /// * `field` - field belonging to the quarter @@ -636,4 +674,13 @@ mod tests { ]); assert!(board.is_quarter_fillable(Color::Black, 16)); } + + #[test] + fn get_quarter_filling_candidate() { + let mut board = Board::new(); + board.set_positions([ + 3, 1, 2, 2, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + assert_eq!(vec![2], board.get_quarter_filling_candidate(Color::White)); + } } diff --git a/store/src/game_rules_moves.rs b/store/src/game_rules_moves.rs index 9787218..0e0a8d5 100644 --- a/store/src/game_rules_moves.rs +++ b/store/src/game_rules_moves.rs @@ -41,12 +41,22 @@ pub struct MoveRules { impl MoveRules { /// Revert board if color is black pub fn new(color: &Color, board: &Board, dice: Dice) -> Self { - let board = if *color == Color::Black { + Self { + board: Self::get_board_from_color(color, board), + dice, + } + } + + pub fn set_board(&mut self, color: &Color, board: &Board) { + self.board = Self::get_board_from_color(color, board); + } + + fn get_board_from_color(color: &Color, board: &Board) -> Board { + if *color == Color::Black { board.mirror() } else { board.clone() - }; - Self { board, dice } + } } pub fn moves_follow_rules(&self, moves: &(CheckerMove, CheckerMove)) -> bool { @@ -155,7 +165,6 @@ impl MoveRules { // Si possible, les deux dés doivent être joués if moves.0.get_from() == 0 || moves.1.get_from() == 0 { let mut possible_moves_sequences = self.get_possible_moves_sequences(true); - println!("{:?}", possible_moves_sequences); possible_moves_sequences.retain(|moves| self.check_exit_rules(moves).is_ok()); // possible_moves_sequences.retain(|moves| self.check_corner_rules(moves).is_ok()); if !possible_moves_sequences.contains(&moves) && !possible_moves_sequences.is_empty() { @@ -313,14 +322,66 @@ impl MoveRules { moves_seqs } + pub fn get_scoring_quarter_filling_moves_sequences(&self) -> Vec<(CheckerMove, CheckerMove)> { + let all_seqs = self.get_quarter_filling_moves_sequences(); + if all_seqs.len() == 0 { + return vec![]; + } + let missing_fields = self.board.get_quarter_filling_candidate(Color::White); + match missing_fields.len() { + // preserve an already filled quarter : return one sequence + 0 => vec![*all_seqs.last().unwrap()], + // two fields, two dices : all_seqs should already contain only one possibility + 2 => all_seqs, + 1 => { + let dest_field = missing_fields.first().unwrap(); + let mut filling_moves_origins = vec![]; + all_seqs.iter().fold(vec![], |mut acc, seq| { + let origins = self.get_sequence_origin_from_destination(*seq, *dest_field); + for origin in origins { + if !filling_moves_origins.contains(&origin) { + filling_moves_origins.push(origin); + acc.push(*seq); + } + } + acc + }) + } + _ => vec![], // cannot be + } + } + + fn get_sequence_origin_from_destination( + &self, + sequence: (CheckerMove, CheckerMove), + destination: Field, + ) -> Vec { + let mut origin = vec![]; + if sequence.0.get_to() == destination { + origin.push(sequence.0.get_from()); + } + if sequence.1.get_to() == destination { + if sequence.0.get_to() == sequence.1.get_from() { + // tout d'une + origin.push(sequence.0.get_from()); + } else { + origin.push(sequence.1.get_from()); + } + } + origin + } + + // Get all moves filling a quarter or preserving a filled quarter pub fn get_quarter_filling_moves_sequences(&self) -> Vec<(CheckerMove, CheckerMove)> { let mut moves_seqs = Vec::new(); let color = &Color::White; + let all_moves_seqs = self.get_possible_moves_sequences(true); for moves in self.get_possible_moves_sequences(true) { let mut board = self.board.clone(); board.move_checker(color, moves.0).unwrap(); board.move_checker(color, moves.1).unwrap(); - if board.any_quarter_filled(*color) { + // println!("get_quarter_filling_moves_sequences board : {:?}", board); + if board.any_quarter_filled(*color) && !moves_seqs.contains(&moves) { moves_seqs.push(moves); } } @@ -811,4 +872,57 @@ mod tests { ); assert!(state.moves_possible(&moves)); } + + #[test] + fn filling_moves_sequences() { + let mut state = MoveRules::default(); + state.board.set_positions([ + 3, 3, 1, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + state.dice.values = (2, 1); + let filling_moves_sequences = state.get_quarter_filling_moves_sequences(); + // println!( + // "test filling_moves_sequences : {:?}", + // filling_moves_sequences + // ); + assert_eq!(2, filling_moves_sequences.len()); + + state.board.set_positions([ + 3, 2, 3, 2, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + state.dice.values = (2, 2); + let filling_moves_sequences = state.get_quarter_filling_moves_sequences(); + // println!("{:?}", filling_moves_sequences); + assert_eq!(2, filling_moves_sequences.len()); + + state.board.set_positions([ + 3, 1, 2, 2, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + state.dice.values = (2, 1); + let filling_moves_sequences = state.get_quarter_filling_moves_sequences(); + // println!( + // "test filling_moves_sequences 2 : {:?}", + // filling_moves_sequences + // ); + assert_eq!(2, filling_moves_sequences.len()); + } + + #[test] + fn scoring_filling_moves_sequences() { + let mut state = MoveRules::default(); + + state.board.set_positions([ + 3, 1, 2, 2, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + state.dice.values = (2, 1); + assert_eq!(1, state.get_scoring_quarter_filling_moves_sequences().len()); + + state.board.set_positions([ + 2, 3, 3, 3, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + state.dice.values = (2, 1); + let filling_moves_sequences = state.get_scoring_quarter_filling_moves_sequences(); + // println!("{:?}", filling_moves_sequences); + assert_eq!(3, filling_moves_sequences.len()); + } } diff --git a/store/src/game_rules_points.rs b/store/src/game_rules_points.rs index 7b9a3b4..4be14c3 100644 --- a/store/src/game_rules_points.rs +++ b/store/src/game_rules_points.rs @@ -104,13 +104,36 @@ impl PointsRules { } } - fn get_jans(&self, board_ini: &Board, dices: &Vec) -> PossibleJans { - let mut dices_reversed = dices.clone(); - dices_reversed.reverse(); + pub fn set_dice(&mut self, dice: Dice) { + self.dice = dice; + self.move_rules.dice = dice; + } + pub fn update_positions(&mut self, positions: [i8; 24]) { + self.board.set_positions(positions); + self.move_rules.board.set_positions(positions); + } + + fn get_jans(&self, board_ini: &Board) -> PossibleJans { + let dices = &vec![self.dice.values.0, self.dice.values.1]; + let dices_reversed = &vec![self.dice.values.1, self.dice.values.0]; + + // « JAN DE RÉCOMPENSE » + // Battre à vrai une dame située dans la table des grands jans + // Battre à vrai une dame située dans la table des petits jans let mut jans = self.get_jans_by_dice_order(board_ini, dices); - let jans_revert_dices = self.get_jans_by_dice_order(board_ini, &dices_reversed); + let jans_revert_dices = self.get_jans_by_dice_order(board_ini, dices_reversed); jans.merge(jans_revert_dices); + + // « JAN DE REMPLISSAGE » + // Faire un petit jan, un grand jan ou un jan de retour + let filling_moves_sequences = self + .move_rules + .get_scoring_quarter_filling_moves_sequences(); + if !filling_moves_sequences.is_empty() { + jans.insert(Jan::FilledQuarter, filling_moves_sequences); + } + jans } @@ -192,14 +215,20 @@ impl PointsRules { pub fn get_points(&self) -> i8 { let mut points: i8 = 0; - let jans = self.get_jans(&self.board, &vec![self.dice.values.0, self.dice.values.1]); + // « JAN DE RÉCOMPENSE » + // Battre à vrai une dame située dans la table des grands jans + // Battre à vrai une dame située dans la table des petits jans + // TODO : Battre le coin adverse + let jans = self.get_jans(&self.board); points += jans.into_iter().fold(0, |acc: i8, (jan, moves)| { acc + jan.get_points(self.dice.is_double()) * (moves.len() as i8) }); - // Jans de remplissage - let filling_moves_sequences = self.move_rules.get_quarter_filling_moves_sequences(); - points += 4 * filling_moves_sequences.len() as i8; + // « JAN DE REMPLISSAGE » + // Faire un petit jan, un grand jan ou un jan de retour + // let filling_moves_sequences = self.move_rules.get_quarter_filling_moves_sequences(); + // points += 4 * filling_moves_sequences.len() as i8; + // cf. https://fr.wikipedia.org/wiki/Trictrac // Points par simple par moyen | Points par doublet par moyen Nombre de moyens possibles Bénéficiaire // « JAN RARE » @@ -208,19 +237,12 @@ impl PointsRules { // Jan de mézéas 4 6 1 Joueur // Contre jan de deux tables 4 6 1 Adversaire // Contre jan de mézéas 4 6 1 Adversaire - // « JAN DE RÉCOMPENSE » - // Battre à vrai une dame située dans la table des grands jans 2 | 4 1, 2 ou 3 (sauf doublet) Joueur - // Battre à vrai une dame située dans la table des petits jans 4 | 6 1, 2 ou 3 Joueur - // Battre le coin adverse 4 6 1 Joueur // « JAN QUI NE PEUT » // Battre à faux une dame // située dans la table des grands jans 2 4 1 Adversaire // Battre à faux une dame // située dans la table des petits jans 4 6 1 Adversaire // Pour chaque dé non jouable (dame impuissante) 2 2 n/a Adversaire - // « JAN DE REMPLISSAGE » - // Faire un petit jan, un grand jan ou un jan de retour 4 1, 2, ou 3 Joueur - // 6 1 ou 2 Joueur // Conserver un petit jan, un grand jan ou un jan de retour 4 6 1 Joueur // « AUTRE » // Sortir le premier toutes ses dames 4 6 n/a Joueur @@ -320,11 +342,39 @@ mod tests { #[test] fn get_points() { + // ----- Jan de récompense let mut rules = PointsRules::default(); - rules.board.set_positions([ + rules.update_positions([ 2, 0, -1, -1, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); - rules.dice = Dice { values: (2, 3) }; + rules.set_dice(Dice { values: (2, 3) }); assert_eq!(12, rules.get_points()); + + // ---- Jan de remplissage + rules.update_positions([ + 3, 1, 2, 2, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + rules.set_dice(Dice { values: (2, 1) }); + assert_eq!(1, rules.get_jans(&rules.board).len()); + assert_eq!(4, rules.get_points()); + + rules.update_positions([ + 2, 3, 1, 2, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + rules.set_dice(Dice { values: (1, 1) }); + assert_eq!(6, rules.get_points()); + + rules.update_positions([ + 3, 3, 1, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + rules.set_dice(Dice { values: (1, 1) }); + assert_eq!(12, rules.get_points()); + + // conservation jan rempli + rules.update_positions([ + 3, 3, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + rules.set_dice(Dice { values: (1, 1) }); + assert_eq!(6, rules.get_points()); } }