diff --git a/store/src/board.rs b/store/src/board.rs index e02720d..17f0c6a 100644 --- a/store/src/board.rs +++ b/store/src/board.rs @@ -81,6 +81,11 @@ impl CheckerMove { pub fn is_exit(&self) -> bool { self.to == 0 && self != &EMPTY_MOVE } + + pub fn doable_with_dice(&self, dice: usize) -> bool { + (self.to == 0 && 25 - self.from <= dice) + || (self.from < self.to && self.to - self.from == dice) + } } /// Represents the Tric Trac board @@ -303,7 +308,7 @@ impl Board { // the square is blocked on the opponent rest corner let opp_corner_field = if color == &Color::White { 13 } else { 12 }; self.passage_blocked(color, field) - // .map(|blocked| blocked || opp_corner_field == field) + // .map(|blocked| blocked || opp_corner_field == field) } pub fn passage_blocked(&self, color: &Color, field: Field) -> Result { diff --git a/store/src/game_rules_moves.rs b/store/src/game_rules_moves.rs index b4543c4..a537d03 100644 --- a/store/src/game_rules_moves.rs +++ b/store/src/game_rules_moves.rs @@ -227,7 +227,7 @@ impl MoveRules { // Oponnent corner let corner_field: Field = self.board.get_color_corner(&Color::Black); - if to1 == corner_field || ( to0 == corner_field && to0 != from1 ) { + if to1 == corner_field || (to0 == corner_field && to0 != from1) { return Err(MoveError::OpponentCorner); } @@ -315,7 +315,7 @@ impl MoveRules { }; let mut moves_seqs = self.get_possible_moves_sequences_by_dices(dice_max, dice_min, with_excedents, false); - // if we got valid sequences whith the highest die, we don't accept sequences using only the + // 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( @@ -679,6 +679,29 @@ mod tests { Err(MoveError::OpponentCanFillQuarter), state.moves_allowed(&moves) ); + + state.board.set_positions([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, -12, 0, 0, 0, 0, 1, 0, + ]); + state.dice.values = (5, 5); + let moves = ( + CheckerMove::new(11, 16).unwrap(), + CheckerMove::new(16, 21).unwrap(), + ); + assert!(state.moves_allowed(&moves).is_ok()); + + state.board.set_positions([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, -12, + ]); + state.dice.values = (5, 5); + let moves = ( + CheckerMove::new(11, 16).unwrap(), + CheckerMove::new(16, 21).unwrap(), + ); + assert_eq!( + Err(MoveError::OpponentCanFillQuarter), + state.moves_allowed(&moves) + ); } #[test] @@ -953,7 +976,7 @@ mod tests { // ); assert_eq!(2, filling_moves_sequences.len()); - // positions + // positions state.board.set_positions([ 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, -2, 0, 0, 0, -2, 0, -2, -2, -2, -2, -3, ]); @@ -1006,10 +1029,7 @@ mod tests { CheckerMove::new(6, 12).unwrap(), CheckerMove::new(7, 12).unwrap(), ); - assert_eq!( - Err(MoveError::MustFillQuarter), - state.moves_allowed(&moves) - ); + assert_eq!(Err(MoveError::MustFillQuarter), state.moves_allowed(&moves)); // seul mouvement possible let moves = ( @@ -1017,9 +1037,23 @@ mod tests { CheckerMove::new(13, 19).unwrap(), ); println!("{:?}", state.moves_allowed(&moves)); - assert!( state.moves_allowed(&moves).is_ok()); - + assert!(state.moves_allowed(&moves).is_ok()); // s'il n'y a pas d'autre solution, on peut rompre } + + #[test] + fn get_possible_moves_sequences() { + let mut state = MoveRules::default(); + + state.board.set_positions([ + 2, 0, -2, -2, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + state.dice.values = (2, 3); + let moves = ( + CheckerMove::new(9, 11).unwrap(), + CheckerMove::new(11, 14).unwrap(), + ); + assert_eq!(vec![moves], state.get_possible_moves_sequences(true)); + } } diff --git a/store/src/game_rules_points.rs b/store/src/game_rules_points.rs index f6892cf..187e3c9 100644 --- a/store/src/game_rules_points.rs +++ b/store/src/game_rules_points.rs @@ -83,7 +83,42 @@ impl PossibleJansMethods for PossibleJans { fn merge(&mut self, other: Self) { for (jan, cmoves_list) in other { for cmoves in cmoves_list { - self.push(jan.clone(), cmoves); + // pour un même mouvement, le battage à vrai est prioritaire sur le battage à faux. + match jan { + Jan::FalseHitBigJan => { + let mut has_true_hit = false; + if let Some(true_moves) = self.get(&Jan::TrueHitBigJan) { + has_true_hit = true_moves.contains(&cmoves); + } + if !has_true_hit { + self.push(jan.clone(), cmoves); + } + } + Jan::FalseHitSmallJan => { + let mut has_true_hit = false; + if let Some(true_moves) = self.get(&Jan::TrueHitSmallJan) { + has_true_hit = true_moves.contains(&cmoves); + } + if !has_true_hit { + self.push(jan.clone(), cmoves); + } + } + Jan::TrueHitBigJan => { + if let Some(false_moves) = self.get_mut(&Jan::FalseHitBigJan) { + false_moves.retain(|fmoves| *fmoves != cmoves); + } + self.push(jan.clone(), cmoves); + } + Jan::TrueHitSmallJan => { + if let Some(false_moves) = self.get_mut(&Jan::FalseHitSmallJan) { + false_moves.retain(|fmoves| *fmoves != cmoves); + } + self.push(jan.clone(), cmoves); + } + _ => { + self.push(jan.clone(), cmoves); + } + } } } } @@ -129,12 +164,15 @@ impl PointsRules { 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]; + let dice1 = self.dice.values.0 as usize; + let dice2 = self.dice.values.1 as usize; // « 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_ordered_dice(board_ini, dices); - let jans_revert_dices = self.get_jans_by_ordered_dice(board_ini, dices_reversed); + let mut jans = self.get_jans_by_ordered_dice(board_ini, dices, None, false); + let jans_revert_dices = + self.get_jans_by_ordered_dice(board_ini, dices_reversed, None, false); jans.merge(jans_revert_dices); // Battre à vrai le coin de repos de l'adversaire @@ -249,8 +287,6 @@ impl PointsRules { if !talon.is_empty() && talon[0].1 == 13 && candidates_fields.len() == 2 { let field1 = candidates_fields[0]; let field2 = candidates_fields[1]; - let dice1 = self.dice.values.0 as usize; - let dice2 = self.dice.values.1 as usize; // Jan de 2 tables et contre jan de 2 tables let jan = if adv_corner_count == 0 { @@ -291,10 +327,36 @@ impl PointsRules { } } + // Jan qui ne peut : dés non jouables + let poss = self.move_rules.get_possible_moves_sequences(true); + let moves = poss.iter().fold(vec![], |mut acc, (m1, m2)| { + acc.push(*m1); + acc.push(*m2); + acc + }); + let moves_dice1: Vec<&CheckerMove> = + moves.iter().filter(|m| m.doable_with_dice(dice1)).collect(); + let moves_dice2: Vec<&CheckerMove> = + moves.iter().filter(|m| m.doable_with_dice(dice2)).collect(); + if poss.is_empty() { + jans.insert( + Jan::HelplessMan, + vec![(CheckerMove::default(), CheckerMove::default())], + ); + } else if moves_dice1.is_empty() || moves_dice2.is_empty() { + jans.insert(Jan::HelplessMan, vec![poss[0]]); + } + jans } - fn get_jans_by_ordered_dice(&self, board_ini: &Board, dices: &Vec) -> PossibleJans { + fn get_jans_by_ordered_dice( + &self, + board_ini: &Board, + dices: &Vec, + only_from: Option, + only_false_hit: bool, + ) -> PossibleJans { let mut jans = PossibleJans::default(); let mut dices = dices.clone(); if let Some(dice) = dices.pop() { @@ -302,39 +364,50 @@ impl PointsRules { let mut board = board_ini.clone(); let corner_field = board.get_color_corner(&color); let adv_corner_field = board.get_color_corner(&Color::Black); - for (from, _) in board.get_color_fields(color) { + let froms = if let Some(from) = only_from { + vec![from] + } else { + board + .get_color_fields(color) + .iter() + .map(|cf| cf.0) + .collect() + }; + for from in froms { + // for (from, _) in board.get_color_fields(color) { let to = if from + dice as usize > 24 { 0 } else { from + dice as usize }; if let Ok(cmove) = CheckerMove::new(from, to) { - // let res = state.moves_allowed(&moves); - // if res.is_ok() { - // println!("dice : {:?}, res : {:?}", dice, res); + // print!( + // " ", + // dice, from, to + // ); // On vérifie que le mouvement n'est pas interdit par les règles des coins de // repos : // - on ne va pas sur le coin de l'adversaire // - ni sur son propre coin de repos avec une seule dame // - règle non prise en compte pour le battage des dames : on ne sort pas de son coin de repos s'il n'y reste que deux dames let (corner_count, _color) = board.get_field_checkers(corner_field).unwrap(); - if to != adv_corner_field && (to != corner_field || corner_count > 1) - // && (from != corner_field || corner_count > 2) - { - // println!( - // "dice : {}, adv_corn_field : {:?}, from : {}, to : {}, corner_count : {}", - // dice, adv_corner_field, from, to, corner_count - // ); - let mut can_try_toutdune = true; + if to != adv_corner_field && (to != corner_field || corner_count > 1) { + // si only_false_hit est vrai, on est déja dans une tentative tout d'une + let mut can_try_toutdune = !only_false_hit; + let mut only_falsehit = false; match board.move_checker(&color, cmove) { Err(Error::FieldBlockedByOne) => { - let jan = if Board::is_field_in_small_jan(to) { - Jan::TrueHitSmallJan - } else { - Jan::TrueHitBigJan + let jan = match (Board::is_field_in_small_jan(to), only_false_hit) { + (true, false) => Jan::TrueHitSmallJan, + (true, true) => Jan::FalseHitSmallJan, + (false, false) => Jan::TrueHitBigJan, + (false, true) => Jan::FalseHitBigJan, }; jans.push(jan, (cmove, EMPTY_MOVE)); } + Err(Error::FieldBlocked) => { + only_falsehit = true; + } Err(_) => { can_try_toutdune = false; // let next_dice_jan = self.get_jans(&board, &dices); @@ -347,15 +420,19 @@ impl PointsRules { // Try tout d'une : // - use original board before first die move // - use a virtual dice by adding current dice to remaining dice + // - limit the checker to the current one let next_dice_jan = self.get_jans_by_ordered_dice( &board_ini, &dices.iter().map(|d| d + dice).collect(), + Some(from), + only_falsehit, ); jans.merge(next_dice_jan); } } // Second die - let next_dice_jan = self.get_jans_by_ordered_dice(&board_ini, &dices); + let next_dice_jan = + self.get_jans_by_ordered_dice(&board_ini, &dices, None, false); jans.merge(next_dice_jan); } } @@ -370,7 +447,12 @@ impl PointsRules { .into_iter() .fold((0, 0), |acc: (i8, i8), (jan, moves)| { println!("get_points : {:?}", jan); - let points = jan.get_points(self.dice.is_double()) * (moves.len() as i8); + let is_double = if jan == Jan::HelplessMan { + moves[0] == (CheckerMove::default(), CheckerMove::default()) + } else { + self.dice.is_double() + }; + let points = jan.get_points(is_double) * (moves.len() as i8); if points < 0 { (acc.0, acc.1 - points) } else { @@ -392,11 +474,11 @@ mod tests { 2, 0, -1, -1, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); - let jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![2, 3]); + let jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![2, 3], None, false); assert_eq!(1, jans.len()); assert_eq!(3, jans.get(&Jan::TrueHitSmallJan).unwrap().len()); - let jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![2, 2]); + let jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![2, 2], None, false); assert_eq!(1, jans.len()); assert_eq!(1, jans.get(&Jan::TrueHitSmallJan).unwrap().len()); @@ -406,9 +488,10 @@ mod tests { 2, 0, -1, -2, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); - let mut jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![2, 3]); - let jans_revert_dices = rules.get_jans_by_ordered_dice(&rules.board, &vec![3, 2]); - assert_eq!(1, jans.len()); + let mut jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![2, 3], None, false); + let jans_revert_dices = + rules.get_jans_by_ordered_dice(&rules.board, &vec![3, 2], None, false); + assert_eq!(2, jans.len()); assert_eq!(1, jans_revert_dices.len()); jans.merge(jans_revert_dices); assert_eq!(2, jans.get(&Jan::TrueHitSmallJan).unwrap().len()); @@ -417,7 +500,7 @@ mod tests { 2, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); - let jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![2, 3]); + let jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![2, 3], None, false); assert_eq!(1, jans.len()); assert_eq!(2, jans.get(&Jan::TrueHitSmallJan).unwrap().len()); @@ -425,7 +508,7 @@ mod tests { 2, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); - let jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![2, 3]); + let jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![2, 3], None, false); assert_eq!(1, jans.len()); assert_eq!(1, jans.get(&Jan::TrueHitSmallJan).unwrap().len()); @@ -433,7 +516,7 @@ mod tests { 2, 0, 1, 1, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); - let jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![2, 3]); + let jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![2, 3], None, false); assert_eq!(1, jans.len()); assert_eq!(3, jans.get(&Jan::TrueHitSmallJan).unwrap().len()); @@ -444,7 +527,7 @@ mod tests { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); // le premier dé traité est le dernier du vecteur : 1 - let jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![2, 1]); + let jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![2, 1], None, false); // println!("jans (dés bloqués) : {:?}", jans.get(&Jan::TrueHit)); assert_eq!(0, jans.len()); @@ -452,15 +535,16 @@ mod tests { rules.board.set_positions([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); - let mut jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![3, 3]); + let jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![3, 3], None, false); assert_eq!(1, jans.len()); // premier dé bloqué, mais tout d'une possible en commençant par le second rules.board.set_positions([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); - let mut jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![3, 1]); - let jans_revert_dices = rules.get_jans_by_ordered_dice(&rules.board, &vec![1, 3]); + let mut jans = rules.get_jans_by_ordered_dice(&rules.board, &vec![3, 1], None, false); + let jans_revert_dices = + rules.get_jans_by_ordered_dice(&rules.board, &vec![1, 3], None, false); assert_eq!(1, jans_revert_dices.len()); jans.merge(jans_revert_dices); @@ -489,6 +573,13 @@ mod tests { ]); rules.set_dice(Dice { values: (2, 4) }); assert_eq!(4, rules.get_points().0); + // Battre à vrai une dame située dans la table des grands jans : 2 + let mut rules = PointsRules::default(); + rules.update_positions([ + 2, 0, -2, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + rules.set_dice(Dice { values: (2, 4) }); + assert_eq!((2, 2), rules.get_points()); // Battre à vrai le coin adverse par doublet : 6 rules.update_positions([ @@ -606,8 +697,28 @@ mod tests { assert_eq!((0, 6), rules.get_points()); // ---- JANS QUI NE PEUT - // Battre à faux une dame située dans la table des grands jans - // Battre à faux une dame située dans la table des petits jans + // Battre à faux une dame située dans la table des petits jans + let mut rules = PointsRules::default(); + rules.update_positions([ + 2, 0, -2, -2, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + rules.set_dice(Dice { values: (2, 3) }); + assert_eq!((0, 4), rules.get_points()); + + // Battre à faux une dame située dans la table des grands jans + let mut rules = PointsRules::default(); + rules.update_positions([ + 2, 0, -2, -1, -2, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + rules.set_dice(Dice { values: (2, 4) }); + assert_eq!((0, 2), rules.get_points()); + // Pour chaque dé non jouable (dame impuissante) + let mut rules = PointsRules::default(); + rules.update_positions([ + 2, 0, -2, -2, -2, 0, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + rules.set_dice(Dice { values: (2, 4) }); + assert_eq!((0, 4), rules.get_points()); } }