wip check jans

This commit is contained in:
Henri Bourcereau 2024-05-25 19:56:38 +02:00
parent b528fa3ac6
commit 0df394c0b1
6 changed files with 246 additions and 139 deletions

View file

@ -70,7 +70,8 @@ impl Bot {
} }
fn calculate_points(&self) -> u8 { fn calculate_points(&self) -> u8 {
self.game.get_points().iter().map(|r| r.0).sum() // self.game.get_points().iter().map(|r| r.0).sum()
0
} }
fn choose_move(&self) -> (CheckerMove, CheckerMove) { fn choose_move(&self) -> (CheckerMove, CheckerMove) {

View file

@ -509,10 +509,15 @@ impl Board {
return Ok(()); return Ok(());
} }
let checker_color = self.get_checkers_color(field)?; // let checker_color = self.get_checkers_color(field)?;
let (count, checker_color) = self.get_field_checkers(field)?;
// error if the case contains the other color // error if the case contains the other color
if checker_color.is_some() && Some(color) != checker_color { if checker_color.is_some() && Some(color) != checker_color {
return Err(Error::FieldInvalid); return if count > 1 {
Err(Error::FieldBlocked)
} else {
Err(Error::FieldBlockedByOne)
};
} }
let unit = match color { let unit = match color {
Color::White => 1, Color::White => 1,

View file

@ -14,6 +14,8 @@ pub enum Error {
PlayerInvalid, PlayerInvalid,
/// Field blocked /// Field blocked
FieldBlocked, FieldBlocked,
/// Field blocked
FieldBlockedByOne,
/// Invalid field /// Invalid field
FieldInvalid, FieldInvalid,
/// Not your turn /// Not your turn
@ -40,6 +42,7 @@ impl fmt::Display for Error {
Error::PlayerInvalid => write!(f, "Invalid player"), Error::PlayerInvalid => write!(f, "Invalid player"),
Error::DoublingNotPermitted => write!(f, "Doubling not permitted"), Error::DoublingNotPermitted => write!(f, "Doubling not permitted"),
Error::FieldBlocked => write!(f, "Field blocked"), Error::FieldBlocked => write!(f, "Field blocked"),
Error::FieldBlockedByOne => write!(f, "Field blocked by one opponent"),
Error::FieldInvalid => write!(f, "Invalid field"), Error::FieldInvalid => write!(f, "Invalid field"),
Error::NotYourTurn => write!(f, "Not your turn"), Error::NotYourTurn => write!(f, "Not your turn"),
Error::MoveInvalid => write!(f, "Invalid move"), Error::MoveInvalid => write!(f, "Invalid move"),

View file

@ -60,15 +60,6 @@ impl fmt::Display for GameState {
} }
} }
impl PointsRules for GameState {
fn board(&self) -> &Board {
&self.board
}
fn dice(&self) -> &Dice {
&self.dice
}
}
impl Default for GameState { impl Default for GameState {
fn default() -> Self { fn default() -> Self {
Self { Self {
@ -248,10 +239,15 @@ impl GameState {
} }
// Check points are correct // Check points are correct
let rules_points: u8 = self.get_points().iter().map(|r| r.0).sum(); // let (board, moves) = if *color == Color::Black {
if rules_points != *points { // (board.mirror(), (moves.0.mirror(), moves.1.mirror()))
return false; // } else {
} // (board.clone(), *moves)
// };
// let rules_points: u8 = self.get_points().iter().map(|r| r.0).sum();
// if rules_points != *points {
// return false;
// }
} }
Move { player_id, moves } => { Move { player_id, moves } => {
// Check player exists // Check player exists
@ -266,8 +262,13 @@ impl GameState {
} }
let color = &self.players[player_id].color; let color = &self.players[player_id].color;
let rules = MoveRules::new(color, &self.board, self.dice, moves); let rules = MoveRules::new(color, &self.board, self.dice);
if !rules.moves_follow_rules() { let moves = if *color == Color::Black {
(moves.0.mirror(), moves.1.mirror())
} else {
*moves
};
if !rules.moves_follow_rules(&moves) {
return false; return false;
} }
} }

View file

@ -36,60 +36,56 @@ pub enum MoveError {
pub struct MoveRules { pub struct MoveRules {
pub board: Board, pub board: Board,
pub dice: Dice, pub dice: Dice,
pub moves: (CheckerMove, CheckerMove),
} }
impl MoveRules { impl MoveRules {
/// Revert board if color is black /// Revert board if color is black
pub fn new( pub fn new(color: &Color, board: &Board, dice: Dice) -> Self {
color: &Color, let board = if *color == Color::Black {
board: &Board, board.mirror()
dice: Dice,
moves: &(CheckerMove, CheckerMove),
) -> Self {
let (board, moves) = if *color == Color::Black {
(board.mirror(), (moves.0.mirror(), moves.1.mirror()))
} else { } else {
(board.clone(), *moves) board.clone()
}; };
Self { board, dice, moves } Self { board, dice }
} }
pub fn moves_follow_rules(&self) -> bool { pub fn moves_follow_rules(&self, moves: &(CheckerMove, CheckerMove)) -> bool {
// Check moves possibles on the board // Check moves possibles on the board
// Check moves conforms to the dice // Check moves conforms to the dice
// Check move is allowed by the rules (to desactivate when playing with schools) // Check move is allowed by the rules (to desactivate when playing with schools)
self.moves_possible() && self.moves_follows_dices() && self.moves_allowed().is_ok() self.moves_possible(moves)
&& self.moves_follows_dices(moves)
&& self.moves_allowed(moves).is_ok()
} }
/// ---- moves_possibles : First of three checks for moves /// ---- moves_possibles : First of three checks for moves
fn moves_possible(&self) -> bool { fn moves_possible(&self, moves: &(CheckerMove, CheckerMove)) -> bool {
let color = &Color::White; let color = &Color::White;
// Check move is physically possible // Check move is physically possible
if !self.board.move_possible(color, &self.moves.0) { if !self.board.move_possible(color, &moves.0) {
return false; return false;
} }
// Chained_move : "Tout d'une" // Chained_move : "Tout d'une"
if let Ok(chained_move) = self.moves.0.chain(self.moves.1) { if let Ok(chained_move) = moves.0.chain(moves.1) {
if !self.board.move_possible(color, &chained_move) { if !self.board.move_possible(color, &chained_move) {
return false; return false;
} }
} else if !self.board.move_possible(color, &self.moves.1) { } else if !self.board.move_possible(color, &moves.1) {
return false; return false;
} }
true true
} }
/// ----- moves_follows_dices : Second of three checks for moves /// ----- moves_follows_dices : Second of three checks for moves
fn moves_follows_dices(&self) -> bool { fn moves_follows_dices(&self, moves: &(CheckerMove, CheckerMove)) -> bool {
// Prise de coin par puissance // Prise de coin par puissance
if self.is_move_by_puissance() { if self.is_move_by_puissance(moves) {
return true; return true;
} }
let (dice1, dice2) = self.dice.values; let (dice1, dice2) = self.dice.values;
let (move1, move2): &(CheckerMove, CheckerMove) = &self.moves; let (move1, move2): &(CheckerMove, CheckerMove) = &moves;
let move1_dices = self.get_move_compatible_dices(move1); let move1_dices = self.get_move_compatible_dices(move1);
if move1_dices.is_empty() { if move1_dices.is_empty() {
@ -144,10 +140,10 @@ impl MoveRules {
} }
/// ---- moves_allowed : Third of three checks for moves /// ---- moves_allowed : Third of three checks for moves
fn moves_allowed(&self) -> Result<(), MoveError> { fn moves_allowed(&self, moves: &(CheckerMove, CheckerMove)) -> Result<(), MoveError> {
self.check_corner_rules(&self.moves)?; self.check_corner_rules(&moves)?;
if self.is_move_by_puissance() { if self.is_move_by_puissance(moves) {
if self.can_take_corner_by_effect() { if self.can_take_corner_by_effect() {
return Err(MoveError::CornerByEffectPossible); return Err(MoveError::CornerByEffectPossible);
} else { } else {
@ -157,17 +153,13 @@ impl MoveRules {
} }
// Si possible, les deux dés doivent être joués // Si possible, les deux dés doivent être joués
let (m1, m2) = self.moves; if moves.0.get_from() == 0 || moves.1.get_from() == 0 {
if m1.get_from() == 0 || m2.get_from() == 0 {
let mut possible_moves_sequences = self.get_possible_moves_sequences(true); let mut possible_moves_sequences = self.get_possible_moves_sequences(true);
println!("{:?}", possible_moves_sequences); println!("{:?}", possible_moves_sequences);
possible_moves_sequences.retain(|moves| self.check_exit_rules(moves).is_ok()); possible_moves_sequences.retain(|moves| self.check_exit_rules(moves).is_ok());
// possible_moves_sequences.retain(|moves| self.check_corner_rules(moves).is_ok()); // possible_moves_sequences.retain(|moves| self.check_corner_rules(moves).is_ok());
// TODO : exclure de ces possibilités celles qui devraient provoquer des CornerNeedsTwoCheckers & ExitNeedsAllCheckersOnLastQuarter... if !possible_moves_sequences.contains(&moves) && !possible_moves_sequences.is_empty() {
if !possible_moves_sequences.contains(&self.moves) if *moves == (EMPTY_MOVE, EMPTY_MOVE) {
&& !possible_moves_sequences.is_empty()
{
if self.moves == (EMPTY_MOVE, EMPTY_MOVE) {
return Err(MoveError::MustPlayAllDice); return Err(MoveError::MustPlayAllDice);
} }
let empty_removed = possible_moves_sequences let empty_removed = possible_moves_sequences
@ -181,10 +173,10 @@ impl MoveRules {
} }
// check exit rules // check exit rules
self.check_exit_rules(&self.moves)?; self.check_exit_rules(moves)?;
// --- interdit de jouer dans cadran que l'adversaire peut encore remplir ---- // --- interdit de jouer dans cadran que l'adversaire peut encore remplir ----
let farthest = cmp::max(self.moves.0.get_to(), self.moves.1.get_to()); let farthest = cmp::max(moves.0.get_to(), moves.1.get_to());
let in_opponent_side = farthest > 12; let in_opponent_side = farthest > 12;
if in_opponent_side && self.board.is_quarter_fillable(Color::Black, farthest) { if in_opponent_side && self.board.is_quarter_fillable(Color::Black, farthest) {
return Err(MoveError::OpponentCanFillQuarter); return Err(MoveError::OpponentCanFillQuarter);
@ -192,7 +184,7 @@ impl MoveRules {
// --- remplir cadran si possible & conserver cadran rempli si possible ---- // --- remplir cadran si possible & conserver cadran rempli si possible ----
let filling_moves_sequences = self.get_quarter_filling_moves_sequences(); let filling_moves_sequences = self.get_quarter_filling_moves_sequences();
if !filling_moves_sequences.contains(&self.moves) && !filling_moves_sequences.is_empty() { if !filling_moves_sequences.contains(moves) && !filling_moves_sequences.is_empty() {
return Err(MoveError::MustFillQuarter); return Err(MoveError::MustFillQuarter);
} }
// no rule was broken // no rule was broken
@ -290,7 +282,7 @@ impl MoveRules {
Ok(()) Ok(())
} }
fn get_possible_moves_sequences( pub fn get_possible_moves_sequences(
&self, &self,
with_excedents: bool, with_excedents: bool,
) -> Vec<(CheckerMove, CheckerMove)> { ) -> Vec<(CheckerMove, CheckerMove)> {
@ -321,7 +313,7 @@ impl MoveRules {
moves_seqs moves_seqs
} }
fn get_quarter_filling_moves_sequences(&self) -> Vec<(CheckerMove, CheckerMove)> { pub fn get_quarter_filling_moves_sequences(&self) -> Vec<(CheckerMove, CheckerMove)> {
let mut moves_seqs = Vec::new(); let mut moves_seqs = Vec::new();
let color = &Color::White; let color = &Color::White;
for moves in self.get_possible_moves_sequences(true) { for moves in self.get_possible_moves_sequences(true) {
@ -414,11 +406,10 @@ impl MoveRules {
moves moves
} }
fn is_move_by_puissance(&self) -> bool { fn is_move_by_puissance(&self, moves: &(CheckerMove, CheckerMove)) -> bool {
let (dice1, dice2) = self.dice.values; let (dice1, dice2) = self.dice.values;
let (move1, move2): &(CheckerMove, CheckerMove) = &self.moves; let dist1 = (moves.0.get_to() as i8 - moves.0.get_from() as i8).unsigned_abs();
let dist1 = (move1.get_to() as i8 - move1.get_from() as i8).unsigned_abs(); let dist2 = (moves.1.get_to() as i8 - moves.1.get_from() as i8).unsigned_abs();
let dist2 = (move2.get_to() as i8 - move2.get_from() as i8).unsigned_abs();
// Both corners must be empty // Both corners must be empty
let (count1, _color) = self.board.get_field_checkers(12).unwrap(); let (count1, _color) = self.board.get_field_checkers(12).unwrap();
@ -428,8 +419,8 @@ impl MoveRules {
} }
let color = &Color::White; let color = &Color::White;
move1.get_to() == move2.get_to() moves.0.get_to() == moves.1.get_to()
&& move1.get_to() == self.board.get_color_corner(color) && moves.0.get_to() == self.board.get_color_corner(color)
&& (cmp::min(dist1, dist2) == cmp::min(dice1, dice2) - 1 && (cmp::min(dist1, dist2) == cmp::min(dice1, dice2) - 1
&& cmp::max(dist1, dist2) == cmp::max(dice1, dice2) - 1) && cmp::max(dist1, dist2) == cmp::max(dice1, dice2) - 1)
} }
@ -487,20 +478,20 @@ mod tests {
10, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -15, 10, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -15,
]); ]);
state.dice.values = (5, 5); state.dice.values = (5, 5);
state.moves = ( let moves = (
CheckerMove::new(8, 12).unwrap(), CheckerMove::new(8, 12).unwrap(),
CheckerMove::new(8, 12).unwrap(), CheckerMove::new(8, 12).unwrap(),
); );
assert!(state.is_move_by_puissance()); assert!(state.is_move_by_puissance(&moves));
assert!(state.moves_follows_dices()); assert!(state.moves_follows_dices(&moves));
assert!(state.moves_allowed().is_ok()); assert!(state.moves_allowed(&moves).is_ok());
// opponent corner must be empty // opponent corner must be empty
state.board.set_positions([ state.board.set_positions([
10, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -13, 10, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -13,
]); ]);
assert!(!state.is_move_by_puissance()); assert!(!state.is_move_by_puissance(&moves));
assert!(!state.moves_follows_dices()); assert!(!state.moves_follows_dices(&moves));
// Si on a la possibilité de prendre son coin à la fois par effet, c'est à dire naturellement, et aussi par puissance, on doit le prendre par effet // Si on a la possibilité de prendre son coin à la fois par effet, c'est à dire naturellement, et aussi par puissance, on doit le prendre par effet
state.board.set_positions([ state.board.set_positions([
@ -508,15 +499,15 @@ mod tests {
]); ]);
assert_eq!( assert_eq!(
Err(MoveError::CornerByEffectPossible), Err(MoveError::CornerByEffectPossible),
state.moves_allowed() state.moves_allowed(&moves)
); );
// on a déjà pris son coin : on ne peux plus y deplacer des dames par puissance // on a déjà pris son coin : on ne peux plus y deplacer des dames par puissance
state.board.set_positions([ state.board.set_positions([
8, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -15, 8, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -15,
]); ]);
assert!(!state.is_move_by_puissance()); assert!(!state.is_move_by_puissance(&moves));
assert!(!state.moves_follows_dices()); assert!(!state.moves_follows_dices(&moves));
} }
#[test] #[test]
@ -527,25 +518,25 @@ mod tests {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0,
]); ]);
state.dice.values = (5, 5); state.dice.values = (5, 5);
state.moves = ( let moves = (
CheckerMove::new(20, 0).unwrap(), CheckerMove::new(20, 0).unwrap(),
CheckerMove::new(20, 0).unwrap(), CheckerMove::new(20, 0).unwrap(),
); );
assert!(state.moves_follows_dices()); assert!(state.moves_follows_dices(&moves));
assert!(state.moves_allowed().is_ok()); assert!(state.moves_allowed(&moves).is_ok());
// toutes les dames doivent être dans le jan de retour // toutes les dames doivent être dans le jan de retour
state.board.set_positions([ state.board.set_positions([
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0,
]); ]);
state.dice.values = (5, 5); state.dice.values = (5, 5);
state.moves = ( let moves = (
CheckerMove::new(20, 0).unwrap(), CheckerMove::new(20, 0).unwrap(),
CheckerMove::new(20, 0).unwrap(), CheckerMove::new(20, 0).unwrap(),
); );
assert_eq!( assert_eq!(
Err(MoveError::ExitNeedsAllCheckersOnLastQuarter), Err(MoveError::ExitNeedsAllCheckersOnLastQuarter),
state.moves_allowed() state.moves_allowed(&moves)
); );
// on ne peut pas sortir une dame avec un nombre excédant si on peut en jouer une avec un nombre défaillant // on ne peut pas sortir une dame avec un nombre excédant si on peut en jouer une avec un nombre défaillant
@ -553,39 +544,42 @@ mod tests {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 0, 2, 0, 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); state.dice.values = (5, 5);
state.moves = ( let moves = (
CheckerMove::new(20, 0).unwrap(), CheckerMove::new(20, 0).unwrap(),
CheckerMove::new(23, 0).unwrap(), CheckerMove::new(23, 0).unwrap(),
); );
assert_eq!(Err(MoveError::ExitByEffectPossible), state.moves_allowed()); assert_eq!(
Err(MoveError::ExitByEffectPossible),
state.moves_allowed(&moves)
);
// on doit jouer le nombre excédant le plus éloigné // on doit jouer le nombre excédant le plus éloigné
state.board.set_positions([ state.board.set_positions([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0,
]); ]);
state.dice.values = (5, 5); state.dice.values = (5, 5);
state.moves = ( let moves = (
CheckerMove::new(20, 0).unwrap(), CheckerMove::new(20, 0).unwrap(),
CheckerMove::new(23, 0).unwrap(), CheckerMove::new(23, 0).unwrap(),
); );
assert_eq!(Err(MoveError::ExitNotFasthest), state.moves_allowed()); assert_eq!(Err(MoveError::ExitNotFasthest), state.moves_allowed(&moves));
state.moves = ( let moves = (
CheckerMove::new(20, 0).unwrap(), CheckerMove::new(20, 0).unwrap(),
CheckerMove::new(21, 0).unwrap(), CheckerMove::new(21, 0).unwrap(),
); );
assert!(state.moves_allowed().is_ok()); assert!(state.moves_allowed(&moves).is_ok());
// Cas de la dernière dame // Cas de la dernière dame
state.board.set_positions([ 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, 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); state.dice.values = (5, 5);
state.moves = ( let moves = (
CheckerMove::new(23, 0).unwrap(), CheckerMove::new(23, 0).unwrap(),
CheckerMove::new(0, 0).unwrap(), CheckerMove::new(0, 0).unwrap(),
); );
assert!(state.moves_follows_dices()); assert!(state.moves_follows_dices(&moves));
assert!(state.moves_allowed().is_ok()); assert!(state.moves_allowed(&moves).is_ok());
} }
#[test] #[test]
@ -595,23 +589,23 @@ mod tests {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0,
]); ]);
state.dice.values = (5, 5); state.dice.values = (5, 5);
state.moves = ( let moves = (
CheckerMove::new(11, 16).unwrap(), CheckerMove::new(11, 16).unwrap(),
CheckerMove::new(11, 16).unwrap(), CheckerMove::new(11, 16).unwrap(),
); );
assert!(state.moves_allowed().is_ok()); assert!(state.moves_allowed(&moves).is_ok());
state.board.set_positions([ 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, 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); state.dice.values = (5, 5);
state.moves = ( let moves = (
CheckerMove::new(11, 16).unwrap(), CheckerMove::new(11, 16).unwrap(),
CheckerMove::new(11, 16).unwrap(), CheckerMove::new(11, 16).unwrap(),
); );
assert_eq!( assert_eq!(
Err(MoveError::OpponentCanFillQuarter), Err(MoveError::OpponentCanFillQuarter),
state.moves_allowed() state.moves_allowed(&moves)
); );
} }
@ -622,31 +616,31 @@ mod tests {
3, 3, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 3, 3, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0,
]); ]);
state.dice.values = (5, 4); state.dice.values = (5, 4);
state.moves = ( let moves = (
CheckerMove::new(1, 6).unwrap(), CheckerMove::new(1, 6).unwrap(),
CheckerMove::new(2, 6).unwrap(), CheckerMove::new(2, 6).unwrap(),
); );
assert!(state.moves_allowed().is_ok()); assert!(state.moves_allowed(&moves).is_ok());
state.moves = ( let moves = (
CheckerMove::new(1, 5).unwrap(), CheckerMove::new(1, 5).unwrap(),
CheckerMove::new(2, 7).unwrap(), CheckerMove::new(2, 7).unwrap(),
); );
assert_eq!(Err(MoveError::MustFillQuarter), state.moves_allowed()); assert_eq!(Err(MoveError::MustFillQuarter), state.moves_allowed(&moves));
state.board.set_positions([ state.board.set_positions([
2, 3, 2, 2, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 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, 3); state.dice.values = (2, 3);
state.moves = ( let moves = (
CheckerMove::new(6, 8).unwrap(), CheckerMove::new(6, 8).unwrap(),
CheckerMove::new(6, 9).unwrap(), CheckerMove::new(6, 9).unwrap(),
); );
assert_eq!(Err(MoveError::MustFillQuarter), state.moves_allowed()); assert_eq!(Err(MoveError::MustFillQuarter), state.moves_allowed(&moves));
state.moves = ( let moves = (
CheckerMove::new(2, 4).unwrap(), CheckerMove::new(2, 4).unwrap(),
CheckerMove::new(5, 8).unwrap(), CheckerMove::new(5, 8).unwrap(),
); );
assert!(state.moves_allowed().is_ok()); assert!(state.moves_allowed(&moves).is_ok());
} }
#[test] #[test]
@ -656,17 +650,17 @@ mod tests {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
]); ]);
state.dice.values = (1, 3); state.dice.values = (1, 3);
state.moves = ( let moves = (
CheckerMove::new(22, 0).unwrap(), CheckerMove::new(22, 0).unwrap(),
CheckerMove::new(0, 0).unwrap(), CheckerMove::new(0, 0).unwrap(),
); );
assert_eq!(Err(MoveError::MustPlayAllDice), state.moves_allowed()); assert_eq!(Err(MoveError::MustPlayAllDice), state.moves_allowed(&moves));
state.moves = ( let moves = (
CheckerMove::new(22, 23).unwrap(), CheckerMove::new(22, 23).unwrap(),
CheckerMove::new(23, 0).unwrap(), CheckerMove::new(23, 0).unwrap(),
); );
assert!(state.moves_allowed().is_ok()); assert!(state.moves_allowed(&moves).is_ok());
} }
#[test] #[test]
@ -677,21 +671,21 @@ mod tests {
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
]); ]);
state.dice.values = (2, 1); state.dice.values = (2, 1);
state.moves = ( let moves = (
CheckerMove::new(10, 12).unwrap(), CheckerMove::new(10, 12).unwrap(),
CheckerMove::new(11, 12).unwrap(), CheckerMove::new(11, 12).unwrap(),
); );
assert!(state.moves_follows_dices()); assert!(state.moves_follows_dices(&moves));
assert!(state.moves_allowed().is_ok()); assert!(state.moves_allowed(&moves).is_ok());
// par puissance // par puissance
state.dice.values = (3, 2); state.dice.values = (3, 2);
state.moves = ( let moves = (
CheckerMove::new(10, 12).unwrap(), CheckerMove::new(10, 12).unwrap(),
CheckerMove::new(11, 12).unwrap(), CheckerMove::new(11, 12).unwrap(),
); );
assert!(state.moves_follows_dices()); assert!(state.moves_follows_dices(&moves));
assert!(state.moves_allowed().is_ok()); assert!(state.moves_allowed(&moves).is_ok());
} }
#[test] #[test]
@ -701,31 +695,31 @@ mod tests {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
]); ]);
state.dice.values = (2, 1); state.dice.values = (2, 1);
state.moves = ( let moves = (
CheckerMove::new(0, 0).unwrap(), CheckerMove::new(0, 0).unwrap(),
CheckerMove::new(0, 0).unwrap(), CheckerMove::new(0, 0).unwrap(),
); );
assert!(state.moves_follows_dices()); assert!(state.moves_follows_dices(&moves));
assert!(state.moves_allowed().is_ok()); assert!(state.moves_allowed(&moves).is_ok());
state.board.set_positions([ state.board.set_positions([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
]); ]);
state.dice.values = (2, 1); state.dice.values = (2, 1);
state.moves = ( let moves = (
CheckerMove::new(23, 24).unwrap(), CheckerMove::new(23, 24).unwrap(),
CheckerMove::new(0, 0).unwrap(), CheckerMove::new(0, 0).unwrap(),
); );
assert!(state.moves_follows_dices()); assert!(state.moves_follows_dices(&moves));
// let res = state.moves_allowed(); // let res = state.moves_allowed(&moves);
// println!("{:?}", res); // println!("{:?}", res);
assert!(state.moves_allowed().is_ok()); assert!(state.moves_allowed(&moves).is_ok());
state.moves = ( let moves = (
CheckerMove::new(0, 0).unwrap(), CheckerMove::new(0, 0).unwrap(),
CheckerMove::new(0, 0).unwrap(), CheckerMove::new(0, 0).unwrap(),
); );
assert_eq!(Err(MoveError::MustPlayAllDice), state.moves_allowed()); assert_eq!(Err(MoveError::MustPlayAllDice), state.moves_allowed(&moves));
} }
#[test] #[test]
@ -735,13 +729,13 @@ mod tests {
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0,
]); ]);
state.dice.values = (2, 3); state.dice.values = (2, 3);
state.moves = ( let moves = (
CheckerMove::new(12, 14).unwrap(), CheckerMove::new(12, 14).unwrap(),
CheckerMove::new(1, 4).unwrap(), CheckerMove::new(1, 4).unwrap(),
); );
assert_eq!( assert_eq!(
Err(MoveError::CornerNeedsTwoCheckers), Err(MoveError::CornerNeedsTwoCheckers),
state.moves_allowed() state.moves_allowed(&moves)
); );
} }
@ -752,18 +746,21 @@ mod tests {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, -1, -1, -1, 0, 0, 0, 0, 0, 0,
]); ]);
state.dice.values = (2, 3); state.dice.values = (2, 3);
state.moves = ( let moves = (
CheckerMove::new(12, 14).unwrap(), CheckerMove::new(12, 14).unwrap(),
CheckerMove::new(0, 0).unwrap(), CheckerMove::new(0, 0).unwrap(),
); );
// let poss = state.get_possible_moves_sequences(&Color::White, true); // let poss = state.get_possible_moves_sequences(&Color::White, true);
// println!("{:?}", poss); // println!("{:?}", poss);
assert_eq!(Err(MoveError::MustPlayStrongerDie), state.moves_allowed()); assert_eq!(
state.moves = ( Err(MoveError::MustPlayStrongerDie),
state.moves_allowed(&moves)
);
let moves = (
CheckerMove::new(12, 15).unwrap(), CheckerMove::new(12, 15).unwrap(),
CheckerMove::new(0, 0).unwrap(), CheckerMove::new(0, 0).unwrap(),
); );
assert!(state.moves_allowed().is_ok()); assert!(state.moves_allowed(&moves).is_ok());
} }
#[test] #[test]
@ -771,29 +768,25 @@ mod tests {
let mut state = MoveRules::default(); let mut state = MoveRules::default();
// Chained moves // Chained moves
state.moves = ( let moves = (
CheckerMove::new(1, 5).unwrap(), CheckerMove::new(1, 5).unwrap(),
CheckerMove::new(5, 9).unwrap(), CheckerMove::new(5, 9).unwrap(),
); );
assert!(state.moves_possible()); assert!(state.moves_possible(&moves));
// not chained moves // not chained moves
state.moves = ( let moves = (
CheckerMove::new(1, 5).unwrap(), CheckerMove::new(1, 5).unwrap(),
CheckerMove::new(6, 9).unwrap(), CheckerMove::new(6, 9).unwrap(),
); );
assert!(!state.moves_possible()); assert!(!state.moves_possible(&moves));
// black moves // black moves
let state = MoveRules::new( let state = MoveRules::new(&Color::Black, &Board::default(), Dice::default());
&Color::Black, let moves = (
&Board::default(), CheckerMove::new(24, 20).unwrap().mirror(),
Dice::default(), CheckerMove::new(20, 19).unwrap().mirror(),
&(
CheckerMove::new(24, 20).unwrap(),
CheckerMove::new(20, 19).unwrap(),
),
); );
assert!(state.moves_possible()); assert!(state.moves_possible(&moves));
} }
} }

View file

@ -1,9 +1,13 @@
use crate::board::Board; use crate::board::Board;
use crate::dice::Dice; use crate::dice::Dice;
use crate::game_rules_moves::MoveRules;
use crate::player::Color;
use crate::CheckerMove;
use crate::Error;
#[derive(std::cmp::PartialEq, Debug)] #[derive(std::cmp::PartialEq, Debug)]
pub enum PointsRule { enum Jan {
FilledQuarter, FilledQuarter { points: u8 },
// jans de récompense : // jans de récompense :
// - battre une dame seule (par autant de façons de le faire, y compris // - battre une dame seule (par autant de façons de le faire, y compris
// utilisant une dame du coin de repos) // utilisant une dame du coin de repos)
@ -15,11 +19,111 @@ pub enum PointsRule {
// - si on ne peut pas jouer ses deux dés // - si on ne peut pas jouer ses deux dés
} }
pub trait PointsRules { #[derive(Debug)]
fn board(&self) -> &Board; struct PossibleJan {
fn dice(&self) -> &Dice; pub jan: Jan,
pub ways: Vec<(CheckerMove, CheckerMove)>,
}
fn get_points(&self) -> Vec<(u8, PointsRule)> { /// PointsRules always consider that the current player is White
Vec::new() /// You must use 'mirror' function on board if player is Black
#[derive(Default)]
pub struct PointsRules {
pub board: Board,
pub dice: Dice,
pub move_rules: MoveRules,
}
impl PointsRules {
/// Revert board if color is black
pub fn new(color: &Color, board: &Board, dice: Dice) -> Self {
let board = if *color == Color::Black {
board.mirror()
} else {
board.clone()
};
let move_rules = MoveRules::new(color, &board, dice);
// let move_rules = MoveRules::new(color, &self.board, dice, moves);
Self {
board,
dice,
move_rules,
}
}
fn get_jans(&self, board: &Board, dices: &Vec<u8>) -> Vec<PossibleJan> {
let mut jans = Vec::new();
if dices.is_empty() {
return jans;
}
let color = Color::White;
let mut dices = dices.clone();
let mut board = board.clone();
let fields = board.get_color_fields(color);
if let Some(dice) = dices.pop() {
for (from, _) in fields {
let to = if from + dice as usize > 24 {
0
} else {
from + dice as usize
};
if let Ok(cmove) = CheckerMove::new(from, to) {
match board.move_checker(&color, cmove) {
Err(Error::FieldBlockedByOne) => {
// TODO : prise en puissance
}
Err(_) => {}
Ok(()) => {
// TODO : check if it's a jan
let next_dice_jan = self.get_jans(&board, &dices);
// TODO : merge jans du dé courant et du prochain dé
}
}
}
}
}
// TODO : mouvement en puissance ?
// TODO : tout d'une (sans doublons avec 1 + 1) ?
jans
}
pub fn get_points(&self) -> usize {
let mut points = 0;
let jans = self.get_jans(&self.board, &vec![self.dice.values.0, self.dice.values.1]);
// Jans de remplissage
let filling_moves_sequences = self.move_rules.get_quarter_filling_moves_sequences();
points += 4 * filling_moves_sequences.len();
// Points par simple par moyen Points par doublet par moyen Nombre de moyens possibles Bénéficiaire
// « JAN RARE »
// Jan de six tables 4 n/a 1 Joueur
// Jan de deux tables 4 6 1 Joueur
// 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 1, 2 ou 3 Joueur
// 4 1 ou 2 Joueur
// Battre à vrai une dame
// située dans la table des petits jans 4 1, 2 ou 3 Joueur
// 6 1 ou 2 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
points
} }
} }