fix: exit with farthest rule
This commit is contained in:
parent
de414ebebb
commit
b52f32343d
2 changed files with 223 additions and 73 deletions
|
|
@ -454,7 +454,7 @@ impl Board {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut farthest_exit_move = 25;
|
// let mut farthest_exit_move = 25;
|
||||||
for (field, count) in self.get_color_fields(color) {
|
for (field, count) in self.get_color_fields(color) {
|
||||||
// check rest corner exit
|
// check rest corner exit
|
||||||
if field == self.get_color_corner(&color) && count == 2 && check_rest_corner_exit {
|
if field == self.get_color_corner(&color) && count == 2 && check_rest_corner_exit {
|
||||||
|
|
@ -468,7 +468,7 @@ impl Board {
|
||||||
// if with_excedants && !forbid_exits && field < farthest_exit_move && 2 < count {
|
// if with_excedants && !forbid_exits && field < farthest_exit_move && 2 < count {
|
||||||
if with_excedants && !forbid_exits {
|
if with_excedants && !forbid_exits {
|
||||||
dest = 0;
|
dest = 0;
|
||||||
farthest_exit_move = field;
|
// farthest_exit_move = field;
|
||||||
// println!("farthest is now {farthest_exit_move}");
|
// println!("farthest is now {farthest_exit_move}");
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -346,89 +346,85 @@ impl MoveRules {
|
||||||
|
|
||||||
// The chosen checker must be the farthest from exit
|
// The chosen checker must be the farthest from exit
|
||||||
// For chained moves (tout d'une), we need to check the board state AFTER the first move
|
// For chained moves (tout d'une), we need to check the board state AFTER the first move
|
||||||
let board_to_check = if moves.0.get_to() == moves.1.get_from() {
|
// let board_to_check = if moves.0.get_to() == moves.1.get_from() {
|
||||||
// Chained move: apply first move to get the board state
|
// // Chained move: apply first move to get the board state
|
||||||
let mut board_copy = self.board.clone();
|
// let mut board_copy = self.board.clone();
|
||||||
let _ = board_copy.move_checker(&Color::White, moves.0);
|
// let _ = board_copy.move_checker(&Color::White, moves.0);
|
||||||
board_copy
|
// board_copy
|
||||||
} else {
|
// } else {
|
||||||
self.board.clone()
|
// self.board.clone()
|
||||||
};
|
// };
|
||||||
|
|
||||||
let mut checkers = board_to_check.get_color_fields(Color::White);
|
let mut board_to_check = self.board.clone();
|
||||||
|
let farthest_on_move1 = Self::get_board_exit_farthest(&board_to_check);
|
||||||
|
|
||||||
|
let _ = board_to_check.move_checker(&Color::White, moves.0);
|
||||||
|
let farthest_on_move2 = Self::get_board_exit_farthest(&board_to_check);
|
||||||
|
|
||||||
|
let (is_move1_exedant, is_move2_exedant) = self.move_excedants(moves);
|
||||||
|
if (is_move1_exedant && moves.0.get_from() != farthest_on_move1)
|
||||||
|
|| (is_move2_exedant && moves.1.get_from() != farthest_on_move2)
|
||||||
|
{
|
||||||
|
return Err(MoveError::ExitNotFarthest);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_excedants(&self, moves: &(CheckerMove, CheckerMove)) -> (bool, bool) {
|
||||||
|
let move1to = if moves.0.get_to() == 0 {
|
||||||
|
25
|
||||||
|
} else {
|
||||||
|
moves.0.get_to()
|
||||||
|
};
|
||||||
|
let dist1 = move1to - moves.0.get_from();
|
||||||
|
|
||||||
|
let move2to = if moves.1.get_to() == 0 {
|
||||||
|
25
|
||||||
|
} else {
|
||||||
|
moves.1.get_to()
|
||||||
|
};
|
||||||
|
let dist2 = move2to - moves.1.get_from();
|
||||||
|
|
||||||
|
let dist_min = cmp::min(dist1, dist2);
|
||||||
|
let dist_max = cmp::max(dist1, dist2);
|
||||||
|
|
||||||
|
let dice_min = cmp::min(self.dice.values.0, self.dice.values.1) as usize;
|
||||||
|
let dice_max = cmp::max(self.dice.values.0, self.dice.values.1) as usize;
|
||||||
|
|
||||||
|
let min_excedant = dist_min != 0 && dist_min < dice_min;
|
||||||
|
let max_excedant = dist_max != 0 && dist_max < dice_max;
|
||||||
|
|
||||||
|
if dist_min == dist1 {
|
||||||
|
(min_excedant, max_excedant)
|
||||||
|
} else {
|
||||||
|
(max_excedant, min_excedant)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_board_exit_farthest(board: &Board) -> Field {
|
||||||
|
let mut checkers = board.get_color_fields(Color::White);
|
||||||
checkers.sort_by(|a, b| a.0.cmp(&b.0));
|
checkers.sort_by(|a, b| a.0.cmp(&b.0));
|
||||||
|
|
||||||
|
let mut farthest = 25;
|
||||||
// Check if we have a filled quarter that must be preserved
|
// Check if we have a filled quarter that must be preserved
|
||||||
let has_filled_quarter = board_to_check.any_quarter_filled(Color::White);
|
let has_filled_quarter = board.any_quarter_filled(Color::White);
|
||||||
|
|
||||||
let mut farthest = 24;
|
|
||||||
let mut next_farthest = 24;
|
|
||||||
let mut has_two_checkers = false;
|
|
||||||
|
|
||||||
if has_filled_quarter {
|
if has_filled_quarter {
|
||||||
// When a quarter is filled, we can only exit from fields with >2 checkers
|
// When a quarter is filled, we can only exit from fields with >2 checkers
|
||||||
// Find the farthest field with >2 checkers (removing one won't break the quarter)
|
// Find the farthest field with >2 checkers (removing one won't break the quarter)
|
||||||
let available_checkers: Vec<_> =
|
let available_checkers: Vec<_> =
|
||||||
checkers.iter().filter(|(_, count)| *count > 2).collect();
|
checkers.iter().filter(|(_, count)| *count > 2).collect();
|
||||||
|
|
||||||
if !available_checkers.is_empty() {
|
if !available_checkers.is_empty() {
|
||||||
// Use the farthest available checker (that won't break the quarter)
|
// Use the farthest available checker (that won't break the quarter)
|
||||||
farthest = available_checkers[0].0;
|
farthest = available_checkers[0].0;
|
||||||
if available_checkers[0].1 > 3 {
|
|
||||||
next_farthest = available_checkers[0].0;
|
|
||||||
has_two_checkers = true;
|
|
||||||
} else if available_checkers.len() > 1 {
|
|
||||||
next_farthest = available_checkers[1].0;
|
|
||||||
has_two_checkers = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No fields with >2 checkers, fall back to original logic
|
|
||||||
// This shouldn't happen if MustFillQuarter rule is working correctly
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// No filled quarter to preserve, use original logic
|
if farthest == 25 {
|
||||||
if let Some((field, count)) = checkers.first() {
|
if let Some((field, _)) = checkers.first() {
|
||||||
farthest = *field;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
farthest
|
||||||
// s'il reste au moins deux dames, on vérifie que les plus éloignées soient choisies
|
|
||||||
if has_two_checkers || has_filled_quarter {
|
|
||||||
if moves.0.get_to() == 0 && moves.1.get_to() == 0 {
|
|
||||||
// Deux coups sortants en excédant
|
|
||||||
if cmp::max(moves.0.get_from(), moves.1.get_from()) > next_farthest {
|
|
||||||
return Err(MoveError::ExitNotFarthest);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Un seul coup sortant en excédant le coup sortant doit concerner la plus éloignée du bord
|
|
||||||
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 Err(MoveError::ExitNotFarthest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_possible_moves_sequences(
|
pub fn get_possible_moves_sequences(
|
||||||
|
|
@ -569,21 +565,43 @@ impl MoveRules {
|
||||||
for second_move in
|
for second_move in
|
||||||
board2.get_possible_moves(*color, dice2, with_excedents, true, forbid_exits)
|
board2.get_possible_moves(*color, dice2, with_excedents, true, forbid_exits)
|
||||||
{
|
{
|
||||||
if self.check_corner_rules(&(first_move, second_move)).is_ok()
|
if self
|
||||||
|
.check_corner_rules(&(first_move, second_move))
|
||||||
|
// .inspect_err(|e| {
|
||||||
|
// println!(
|
||||||
|
// " 2nd (corner rule): {:?} - {:?}, {:?}",
|
||||||
|
// e, first_move, second_move
|
||||||
|
// )
|
||||||
|
// })
|
||||||
|
.is_ok()
|
||||||
&& self
|
&& self
|
||||||
.check_opponent_can_fill_quarter_rule(&(first_move, second_move))
|
.check_opponent_can_fill_quarter_rule(&(first_move, second_move))
|
||||||
|
// .inspect_err(|e| {
|
||||||
|
// println!(
|
||||||
|
// " 2nd (op fill quarter): {:?} - {:?}, {:?}",
|
||||||
|
// e, first_move, second_move
|
||||||
|
// )
|
||||||
|
// })
|
||||||
.is_ok()
|
.is_ok()
|
||||||
&& !(self.is_move_by_puissance(&(first_move, second_move))
|
&& !(self.is_move_by_puissance(&(first_move, second_move))
|
||||||
&& self.can_take_corner_by_effect())
|
&& self.can_take_corner_by_effect())
|
||||||
&& (ignored_rules.contains(&TricTracRule::Exit)
|
&& (ignored_rules.contains(&TricTracRule::Exit)
|
||||||
|| self.check_exit_rules(&(first_move, second_move)).is_ok())
|
|| self
|
||||||
|
.check_exit_rules(&(first_move, second_move))
|
||||||
|
// .inspect_err(|e| {
|
||||||
|
// println!(
|
||||||
|
// " 2nd (exit rule): {:?} - {:?}, {:?}",
|
||||||
|
// e, first_move, second_move
|
||||||
|
// )
|
||||||
|
// })
|
||||||
|
.is_ok())
|
||||||
&& (ignored_rules.contains(&TricTracRule::MustFillQuarter)
|
&& (ignored_rules.contains(&TricTracRule::MustFillQuarter)
|
||||||
|| self
|
|| self
|
||||||
.check_must_fill_quarter_rule(&(first_move, second_move))
|
.check_must_fill_quarter_rule(&(first_move, second_move))
|
||||||
// .inspect_err(|e| {
|
// .inspect_err(|e| {
|
||||||
// println!(
|
// println!(
|
||||||
// " 2nd (must fill quar): {:?} - {:?}, {:?}",
|
// " 2nd: {:?} - {:?}, {:?} for {:?}",
|
||||||
// e, first_move, second_move
|
// e, first_move, second_move, self.board
|
||||||
// )
|
// )
|
||||||
// })
|
// })
|
||||||
.is_ok())
|
.is_ok())
|
||||||
|
|
@ -867,6 +885,19 @@ mod tests {
|
||||||
);
|
);
|
||||||
assert!(state.moves_follows_dices(&moves));
|
assert!(state.moves_follows_dices(&moves));
|
||||||
assert!(state.moves_allowed(&moves).is_ok());
|
assert!(state.moves_allowed(&moves).is_ok());
|
||||||
|
|
||||||
|
state.board.set_positions(
|
||||||
|
&Color::White,
|
||||||
|
[
|
||||||
|
-5, -2, -2, -4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 3, 2, 3,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
state.dice.values = (4, 5);
|
||||||
|
let moves = (
|
||||||
|
CheckerMove::new(19, 24).unwrap(),
|
||||||
|
CheckerMove::new(22, 0).unwrap(),
|
||||||
|
);
|
||||||
|
assert!(state.moves_allowed(&moves).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1427,6 +1458,26 @@ mod tests {
|
||||||
vec![moves],
|
vec![moves],
|
||||||
state.get_possible_moves_sequences(true, vec![])
|
state.get_possible_moves_sequences(true, vec![])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let mut board = Board::new();
|
||||||
|
board.set_positions(
|
||||||
|
&crate::Color::White,
|
||||||
|
[
|
||||||
|
-7, -4, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, 0, 0, 0, 0, 0, 3, 2, 1, 2, 3, 4,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let state = MoveRules::new(&Color::White, &board, Dice { values: (2, 6) });
|
||||||
|
let moves = vec![
|
||||||
|
(
|
||||||
|
CheckerMove::new(23, 0).unwrap(),
|
||||||
|
CheckerMove::new(19, 21).unwrap(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
CheckerMove::new(19, 21).unwrap(),
|
||||||
|
CheckerMove::new(23, 0).unwrap(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
assert_eq!(moves, state.get_possible_moves_sequences(true, vec![]));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1454,5 +1505,104 @@ mod tests {
|
||||||
vec![]
|
vec![]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
state.dice.values = (4, 5);
|
||||||
|
state.board.set_positions(
|
||||||
|
&crate::Color::White,
|
||||||
|
[
|
||||||
|
-5, -2, -2, -4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 3, 2, 3,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let moves = vec![(
|
||||||
|
CheckerMove::new(19, 23).unwrap(),
|
||||||
|
CheckerMove::new(22, 0).unwrap(),
|
||||||
|
)];
|
||||||
|
assert_eq!(
|
||||||
|
moves,
|
||||||
|
state.get_possible_moves_sequences_by_dices(
|
||||||
|
state.dice.values.0,
|
||||||
|
state.dice.values.1,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
vec![]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let moves = vec![(
|
||||||
|
CheckerMove::new(19, 24).unwrap(),
|
||||||
|
CheckerMove::new(22, 0).unwrap(),
|
||||||
|
)];
|
||||||
|
assert_eq!(
|
||||||
|
moves,
|
||||||
|
state.get_possible_moves_sequences_by_dices(
|
||||||
|
state.dice.values.1,
|
||||||
|
state.dice.values.0,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
vec![]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut board = Board::new();
|
||||||
|
board.set_positions(
|
||||||
|
&crate::Color::White,
|
||||||
|
[
|
||||||
|
-7, -4, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, 0, 0, 0, 0, 0, 3, 2, 1, 2, 3, 4,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let state = MoveRules::new(&Color::White, &board, Dice { values: (2, 6) });
|
||||||
|
let moves = (
|
||||||
|
CheckerMove::new(19, 21).unwrap(),
|
||||||
|
CheckerMove::new(23, 0).unwrap(),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
vec![moves],
|
||||||
|
state.get_possible_moves_sequences_by_dices(
|
||||||
|
state.dice.values.0,
|
||||||
|
state.dice.values.1,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
vec![]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_exit_rules() {
|
||||||
|
let mut state = MoveRules::default();
|
||||||
|
state.dice.values = (4, 5);
|
||||||
|
state.board.set_positions(
|
||||||
|
&crate::Color::White,
|
||||||
|
[
|
||||||
|
-5, -2, -2, -4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 3, 2, 3,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let moves = (
|
||||||
|
CheckerMove::new(19, 23).unwrap(),
|
||||||
|
CheckerMove::new(22, 0).unwrap(),
|
||||||
|
);
|
||||||
|
assert!(state.check_exit_rules(&moves).is_ok());
|
||||||
|
|
||||||
|
let moves = (
|
||||||
|
CheckerMove::new(19, 24).unwrap(),
|
||||||
|
CheckerMove::new(22, 0).unwrap(),
|
||||||
|
);
|
||||||
|
assert!(state.check_exit_rules(&moves).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_must_fill_quarter_rule() {
|
||||||
|
let mut state = MoveRules::default();
|
||||||
|
state.dice.values = (4, 5);
|
||||||
|
state.board.set_positions(
|
||||||
|
&crate::Color::White,
|
||||||
|
[
|
||||||
|
-5, -2, -2, -4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 3, 2, 3,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let moves = (
|
||||||
|
CheckerMove::new(19, 24).unwrap(),
|
||||||
|
CheckerMove::new(22, 0).unwrap(),
|
||||||
|
);
|
||||||
|
assert!(state.check_must_fill_quarter_rule(&moves).is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue