From 8521de6ec9f10c1d8dccb1ac8f32a0cd8b5383bb Mon Sep 17 00:00:00 2001 From: Henri Bourcereau Date: Fri, 10 May 2024 19:31:53 +0200 Subject: [PATCH] wip exit rules --- store/src/board.rs | 54 +++++++++--- store/src/game.rs | 210 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 237 insertions(+), 27 deletions(-) diff --git a/store/src/board.rs b/store/src/board.rs index 3b03281..da6ec49 100644 --- a/store/src/board.rs +++ b/store/src/board.rs @@ -266,20 +266,8 @@ impl Board { // the square is blocked on the opponent rest corner or if there are opponent's men on the square match color { - Color::White => { - if field == 13 || self.positions[field - 1] < 0 { - Ok(true) - } else { - Ok(false) - } - } - Color::Black => { - if field == 12 || self.positions[23 - field] > 1 { - Ok(true) - } else { - Ok(false) - } - } + Color::White => Ok(field == 13 || self.positions[field - 1] < 0), + Color::Black => Ok(field == 12 || self.positions[23 - field] > 1), } } @@ -330,6 +318,44 @@ impl Board { } } + pub fn get_possible_moves( + &self, + color: Color, + dice: u8, + with_excedants: bool, + ) -> Vec { + let mut moves = Vec::new(); + + let get_dest = |from| { + if color == Color::White { + if from + dice as i32 == 25 { + 0 + } else { + from + dice as i32 + } + } else { + from - dice as i32 + } + }; + + for (field, _count) in self.get_color_fields(color) { + let mut dest = get_dest(field as i32); + if !(0..25).contains(&dest) { + if with_excedants { + dest = 0; + } else { + continue; + } + } + if let Ok(cmove) = CheckerMove::new(field, dest.unsigned_abs() as usize) { + if let Ok(false) = self.blocked(&color, dest.unsigned_abs() as usize) { + moves.push(cmove); + } + } + } + moves + } + pub fn move_possible(&self, color: &Color, cmove: &CheckerMove) -> bool { let blocked = self.blocked(color, cmove.to).unwrap_or(true); // Check if there is a player's checker on the 'from' square diff --git a/store/src/game.rs b/store/src/game.rs index 0f0e5ec..576ffa0 100644 --- a/store/src/game.rs +++ b/store/src/game.rs @@ -373,13 +373,88 @@ impl GameState { return false; } - // ------- exit rules ---------- - // -- toutes les dames doivent être dans le jan de retour - // -- si on peut sortir, on doit sortir - // -- priorité : - // - dame se trouvant sur la flêche correspondant au dé - // - dame se trouvant plus loin de la sortie que la flêche (point défaillant) - // - dame se trouvant plus près que la flêche (point exédant) + // check exit rules + if moves.0.get_to() == 0 || moves.1.get_to() == 0 { + // toutes les dames doivent être dans le jan de retour + let has_outsiders = !self + .board + .get_color_fields(*color) + .iter() + .filter(|(field, _count)| { + (*color == Color::White && *field < 19) + || (*color == Color::Black && *field > 6) + }) + .collect::>() + .is_empty(); + if has_outsiders { + return false; + } + + // les sorties directes sont autorisées, ainsi que les nombre défaillants + let possible_moves_sequences = self.get_possible_moves_sequences(color); + if !possible_moves_sequences.contains(moves) { + // À ce stade au moins un des déplacements concerne un nombre en excédant + // - si d'autres séquences de mouvements sans nombre en excédant étaient possibles, on + // refuse cette séquence + if !possible_moves_sequences.is_empty() { + return false; + } + + // - la dame choisie doit être la plus éloignée de la sortie + let mut checkers = self.board.get_color_fields(*color); + checkers.sort_by(|a, b| { + if *color == Color::White { + b.0.cmp(&a.0) + } else { + a.0.cmp(&b.0) + } + }); + let mut farthest = if *color == Color::White { 24 } else { 1 }; + let mut next_farthest = if *color == Color::White { 24 } else { 1 }; + let mut has_two_checkers = false; + if let Some((field, count)) = checkers.first() { + farthest = *field; + if *count > 1 { + next_farthest = *field; + has_two_checkers = true; + } else if let Some((field, _count)) = checkers.get(1) { + next_farthest = *field; + has_two_checkers = true; + } + } + + if has_two_checkers && moves.0.get_to() == 0 && moves.1.get_to() == 0 { + // Deux coups sortants en excédant + if *color == Color::White { + if cmp::max(moves.0.get_from(), moves.1.get_from()) > next_farthest { + return false; + } + } else if cmp::min(moves.0.get_from(), moves.1.get_from()) < next_farthest { + return false; + } + } else { + // Un seul coup sortant en excédant : soit il ne reste qu'une dame et on fait + // tout d'une, soit il y en a au moins deux et le coup sortant doit concerner + // la plus éloignée du bord + if has_two_checkers { + let exit_move_field = if moves.0.get_to() == 0 { + moves.0.get_from() + } else { + moves.1.get_from() + }; + if exit_move_field != farthest { + return false; + } + } else if *color == Color::White { + if cmp::min(moves.0.get_from(), moves.1.get_from()) != farthest { + return false; + } + } else if cmp::max(moves.0.get_from(), moves.1.get_from()) != farthest { + return false; + } + } + } + } // --- remplir cadran si possible ---- // --- conserver cadran rempli si possible ---- @@ -388,6 +463,77 @@ impl GameState { true } + fn get_possible_moves_sequences(&self, color: &Color) -> Vec<(CheckerMove, CheckerMove)> { + let (dice1, dice2) = self.dice.values; + let mut moves_seqs = self.get_possible_moves_sequences_by_dices(color, dice1, dice2); + let mut moves_seqs_order2 = self.get_possible_moves_sequences_by_dices(color, dice1, dice2); + moves_seqs.append(&mut moves_seqs_order2); + moves_seqs + } + + fn get_possible_moves_sequences_by_dices( + &self, + color: &Color, + dice1: u8, + dice2: u8, + ) -> Vec<(CheckerMove, CheckerMove)> { + let mut moves_seqs = Vec::new(); + for first_move in self.board.get_possible_moves(*color, dice1, false) { + let mut board2 = self.board.clone(); + if board2.move_checker(color, first_move).is_err() { + continue; + } + for second_move in board2.get_possible_moves(*color, dice2, false) { + moves_seqs.push((first_move, second_move)); + } + } + moves_seqs + } + + fn get_direct_exit_moves(&self, color: &Color) -> Vec { + let mut moves = Vec::new(); + let (dice1, dice2) = self.dice.values; + + // sorties directes simples + let (field1_candidate, field2_candidate) = if color == &Color::White { + (25 - dice1 as usize, 25 - dice2 as usize) + } else { + (dice1 as usize, dice2 as usize) + }; + let (count1, col1) = self.board.get_field_checkers(field1_candidate).unwrap(); + let (count2, col2) = self.board.get_field_checkers(field2_candidate).unwrap(); + if count1 > 0 { + moves.push(CheckerMove::new(field1_candidate, 0).unwrap()); + } + if dice2 != dice1 { + if count2 > 0 { + moves.push(CheckerMove::new(field2_candidate, 0).unwrap()); + } + } else if count1 > 1 { + // doublet et deux dames disponibles + moves.push(CheckerMove::new(field1_candidate, 0).unwrap()); + } + + // sortie directe tout d'une + let fieldall_candidate = if color == &Color::White { + 25 - dice1 - dice2 + } else { + dice1 + dice2 + } as usize; + let (countall, _col) = self.board.get_field_checkers(fieldall_candidate).unwrap(); + if countall > 0 { + if col1.is_none() || col1 == Some(color) { + moves.push(CheckerMove::new(fieldall_candidate, field1_candidate).unwrap()); + moves.push(CheckerMove::new(field1_candidate, 0).unwrap()); + } + if col2.is_none() || col2 == Some(color) { + moves.push(CheckerMove::new(fieldall_candidate, field2_candidate).unwrap()); + moves.push(CheckerMove::new(field2_candidate, 0).unwrap()); + } + } + moves + } + fn is_move_by_puissance(&self, color: &Color, moves: &(CheckerMove, CheckerMove)) -> bool { let (dice1, dice2) = self.dice.values; let (move1, move2): &(CheckerMove, CheckerMove) = moves; @@ -596,7 +742,7 @@ mod tests { use super::*; #[test] - fn test_to_string_id() { + fn to_string_id() { let mut state = GameState::default(); state.add_player(1, Player::new("player1".into(), Color::White)); state.add_player(2, Player::new("player2".into(), Color::Black)); @@ -606,7 +752,7 @@ mod tests { } #[test] - fn test_moves_possible() { + fn moves_possible() { let mut state = GameState::default(); let player1 = Player::new("player1".into(), Color::White); let player_id = 1; @@ -639,7 +785,7 @@ mod tests { } #[test] - fn test_moves_follow_dices() { + fn moves_follow_dices() { let mut state = GameState::default(); let player1 = Player::new("player1".into(), Color::White); let player_id = 1; @@ -664,7 +810,7 @@ mod tests { } #[test] - fn test_can_take_corner_by_effect() { + fn can_take_corner_by_effect() { let mut state = GameState::default(); let player1 = Player::new("player1".into(), Color::White); let player_id = 1; @@ -698,7 +844,7 @@ mod tests { } #[test] - fn test_prise_en_puissance() { + fn prise_en_puissance() { let mut state = GameState::default(); let player1 = Player::new("player1".into(), Color::White); let player_id = 1; @@ -744,7 +890,7 @@ mod tests { } #[test] - fn test_exit() { + fn exit() { let mut state = GameState::default(); let player1 = Player::new("player1".into(), Color::White); let player_id = 1; @@ -778,5 +924,43 @@ mod tests { CheckerMove::new(20, 0).unwrap(), ); assert!(!state.moves_allowed(&Color::White, &moves)); + + // on ne peut pas sortir une dame avec un nombre excédant si on peut en jouer une avec un nombre défaillant + state.board.set_positions([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 0, 2, 0, + ]); + state.dice.values = (5, 5); + let moves = ( + CheckerMove::new(20, 0).unwrap(), + CheckerMove::new(23, 0).unwrap(), + ); + assert!(!state.moves_allowed(&Color::White, &moves)); + + // on doit jouer le nombre excédant le plus éloigné + state.board.set_positions([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 1, 0, + ]); + state.dice.values = (5, 5); + let moves = ( + CheckerMove::new(20, 0).unwrap(), + CheckerMove::new(23, 0).unwrap(), + ); + assert!(!state.moves_allowed(&Color::White, &moves)); + let moves = ( + CheckerMove::new(20, 0).unwrap(), + CheckerMove::new(20, 0).unwrap(), + ); + assert!(state.moves_allowed(&Color::White, &moves)); + + // Cas de la dernière dame + state.board.set_positions([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + ]); + state.dice.values = (5, 5); + let moves = ( + CheckerMove::new(23, 0).unwrap(), + CheckerMove::new(0, 0).unwrap(), + ); + assert!(state.moves_allowed(&Color::White, &moves)); } }