trictrac/store/src/game_rules_moves.rs

970 lines
35 KiB
Rust
Raw Normal View History

2024-05-20 18:57:19 +02:00
//! # Play a TricTrac Game
use crate::board::{Board, CheckerMove, Field, EMPTY_MOVE};
use crate::dice::Dice;
use crate::game::GameState;
use crate::player::Color;
use std::cmp;
#[derive(std::cmp::PartialEq, Debug)]
pub enum MoveError {
// 2 checkers must go at the same time on an empty corner
// & the last 2 checkers of a corner must leave at the same time
CornerNeedsTwoCheckers,
// Prise de coin de repos par puissance alors qu'il est possible
// de le prendre directement (par "effet")
CornerByEffectPossible,
// toutes les dames doivent être dans le jan de retour
ExitNeedsAllCheckersOnLastQuarter,
// mouvement avec nombre en exédant alors qu'une séquence de mouvements
// sans nombre en excédant est possible
ExitByEffectPossible,
// Sortie avec nombre en excédant d'une dame qui n'est pas la plus éloignée
ExitNotFasthest,
// Jeu dans un cadran que l'adversaire peut encore remplir
OpponentCanFillQuarter,
// remplir cadran si possible & conserver cadran rempli si possible ----
MustFillQuarter,
2024-05-21 21:22:04 +02:00
// On n'a pas le droit de jouer d'une manière qui empêche de jouer les deux dés si on a la possibilité de les jouer.
MustPlayAllDice,
// Si on ne peut jouer qu'un seul dé, on doit jouer le plus fort si possible.
MustPlayStrongerDie,
2024-05-20 18:57:19 +02:00
}
2024-05-24 14:30:50 +02:00
/// MoveRules always consider that the current player is White
/// You must use 'mirror' functions on board & CheckerMoves if player is Black
#[derive(Default)]
pub struct MoveRules {
pub board: Board,
pub dice: Dice,
}
impl MoveRules {
/// Revert board if color is black
2024-05-25 19:56:38 +02:00
pub fn new(color: &Color, board: &Board, dice: Dice) -> Self {
2024-06-24 21:22:27 +02:00
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 {
2024-05-25 19:56:38 +02:00
board.mirror()
2024-05-24 14:30:50 +02:00
} else {
2024-05-25 19:56:38 +02:00
board.clone()
2024-06-24 21:22:27 +02:00
}
2024-05-24 14:30:50 +02:00
}
2024-05-20 18:57:19 +02:00
2024-05-25 19:56:38 +02:00
pub fn moves_follow_rules(&self, moves: &(CheckerMove, CheckerMove)) -> bool {
2024-05-24 14:30:50 +02:00
// Check moves possibles on the board
// Check moves conforms to the dice
// Check move is allowed by the rules (to desactivate when playing with schools)
2024-05-25 19:56:38 +02:00
self.moves_possible(moves)
&& self.moves_follows_dices(moves)
&& self.moves_allowed(moves).is_ok()
2024-05-24 14:30:50 +02:00
}
/// ---- moves_possibles : First of three checks for moves
2024-05-25 19:56:38 +02:00
fn moves_possible(&self, moves: &(CheckerMove, CheckerMove)) -> bool {
2024-05-24 14:30:50 +02:00
let color = &Color::White;
2024-05-25 19:56:38 +02:00
if let Ok(chained_move) = moves.0.chain(moves.1) {
// Check intermediary move and chained_move : "Tout d'une"
if !self.board.passage_possible(color, &moves.0)
|| !self.board.move_possible(color, &chained_move)
{
2024-05-20 18:57:19 +02:00
return false;
}
} else if !self.board.move_possible(color, &moves.0)
|| !self.board.move_possible(color, &moves.1)
{
// Move is not physically possible
2024-05-20 18:57:19 +02:00
return false;
}
true
}
2024-05-24 14:30:50 +02:00
/// ----- moves_follows_dices : Second of three checks for moves
2024-05-25 19:56:38 +02:00
fn moves_follows_dices(&self, moves: &(CheckerMove, CheckerMove)) -> bool {
2024-05-24 14:30:50 +02:00
// Prise de coin par puissance
2024-05-25 19:56:38 +02:00
if self.is_move_by_puissance(moves) {
2024-05-24 14:30:50 +02:00
return true;
}
let (dice1, dice2) = self.dice.values;
2024-06-24 18:10:24 +02:00
let (move1, move2): &(CheckerMove, CheckerMove) = moves;
2024-05-24 14:30:50 +02:00
let move1_dices = self.get_move_compatible_dices(move1);
if move1_dices.is_empty() {
return false;
}
let move2_dices = self.get_move_compatible_dices(move2);
if move2_dices.is_empty() {
return false;
}
if move1_dices.len() == 1
&& move2_dices.len() == 1
&& move1_dices[0] == move2_dices[0]
&& dice1 != dice2
{
return false;
}
// no rule was broken
true
}
fn get_move_compatible_dices(&self, cmove: &CheckerMove) -> Vec<u8> {
let (dice1, dice2) = self.dice.values;
2024-05-20 18:57:19 +02:00
let mut move_dices = Vec::new();
if cmove.get_to() == 0 {
// handle empty move (0, 0) only one checker left, exiting with the first die.
if cmove.get_from() == 0 {
move_dices.push(dice1);
move_dices.push(dice2);
return move_dices;
}
// Exits
2024-05-24 14:30:50 +02:00
let min_dist = 25 - cmove.get_from();
2024-05-20 18:57:19 +02:00
if dice1 as usize >= min_dist {
move_dices.push(dice1);
}
if dice2 as usize >= min_dist {
move_dices.push(dice2);
}
} else {
let dist = (cmove.get_to() as i8 - cmove.get_from() as i8).unsigned_abs();
if dice1 == dist {
move_dices.push(dice1);
}
if dice2 == dist {
move_dices.push(dice2);
}
}
move_dices
}
2024-05-24 14:30:50 +02:00
/// ---- moves_allowed : Third of three checks for moves
2024-06-14 19:07:33 +02:00
pub fn moves_allowed(&self, moves: &(CheckerMove, CheckerMove)) -> Result<(), MoveError> {
2024-05-25 19:56:38 +02:00
self.check_corner_rules(&moves)?;
2024-05-20 18:57:19 +02:00
2024-05-25 19:56:38 +02:00
if self.is_move_by_puissance(moves) {
2024-05-24 14:30:50 +02:00
if self.can_take_corner_by_effect() {
2024-05-21 21:22:04 +02:00
return Err(MoveError::CornerByEffectPossible);
} else {
// subsequent rules cannot be broken whith a move by puissance
return Ok(());
}
}
2024-05-24 19:23:04 +02:00
2024-05-21 21:22:04 +02:00
// Si possible, les deux dés doivent être joués
2024-05-25 19:56:38 +02:00
if moves.0.get_from() == 0 || moves.1.get_from() == 0 {
2024-05-24 19:23:04 +02:00
let mut possible_moves_sequences = self.get_possible_moves_sequences(true);
possible_moves_sequences.retain(|moves| self.check_exit_rules(moves).is_ok());
// possible_moves_sequences.retain(|moves| self.check_corner_rules(moves).is_ok());
2024-05-25 19:56:38 +02:00
if !possible_moves_sequences.contains(&moves) && !possible_moves_sequences.is_empty() {
if *moves == (EMPTY_MOVE, EMPTY_MOVE) {
2024-05-24 19:23:04 +02:00
return Err(MoveError::MustPlayAllDice);
2024-05-20 18:57:19 +02:00
}
2024-05-24 19:23:04 +02:00
let empty_removed = possible_moves_sequences
.iter()
.filter(|(c1, c2)| *c1 != EMPTY_MOVE && *c2 != EMPTY_MOVE);
if empty_removed.count() > 0 {
return Err(MoveError::MustPlayAllDice);
2024-05-20 18:57:19 +02:00
}
2024-05-24 19:23:04 +02:00
return Err(MoveError::MustPlayStrongerDie);
2024-05-20 18:57:19 +02:00
}
}
2024-05-24 19:23:04 +02:00
// check exit rules
2024-05-25 19:56:38 +02:00
self.check_exit_rules(moves)?;
2024-05-24 19:23:04 +02:00
2024-05-20 18:57:19 +02:00
// --- interdit de jouer dans cadran que l'adversaire peut encore remplir ----
2024-05-25 19:56:38 +02:00
let farthest = cmp::max(moves.0.get_to(), moves.1.get_to());
2024-05-24 14:30:50 +02:00
let in_opponent_side = farthest > 12;
if in_opponent_side && self.board.is_quarter_fillable(Color::Black, farthest) {
2024-05-20 18:57:19 +02:00
return Err(MoveError::OpponentCanFillQuarter);
}
// --- remplir cadran si possible & conserver cadran rempli si possible ----
2024-05-24 14:30:50 +02:00
let filling_moves_sequences = self.get_quarter_filling_moves_sequences();
2024-05-25 19:56:38 +02:00
if !filling_moves_sequences.contains(moves) && !filling_moves_sequences.is_empty() {
2024-05-20 18:57:19 +02:00
return Err(MoveError::MustFillQuarter);
}
// no rule was broken
Ok(())
}
2024-05-24 14:30:50 +02:00
fn check_corner_rules(&self, moves: &(CheckerMove, CheckerMove)) -> Result<(), MoveError> {
let corner_field: Field = self.board.get_color_corner(&Color::White);
let (corner_count, _color) = self.board.get_field_checkers(corner_field).unwrap();
2024-05-23 21:11:02 +02:00
let (from0, to0, from1, to1) = (
moves.0.get_from(),
moves.0.get_to(),
moves.1.get_from(),
moves.1.get_to(),
);
// 2 checkers must go at the same time on an empty corner
if (to0 == corner_field || to1 == corner_field) && (to0 != to1) && corner_count == 0 {
return Err(MoveError::CornerNeedsTwoCheckers);
}
// the last 2 checkers of a corner must leave at the same time
if (from0 == corner_field || from1 == corner_field) && (from0 != from1) && corner_count == 2
{
return Err(MoveError::CornerNeedsTwoCheckers);
}
Ok(())
}
2024-05-24 19:23:04 +02:00
fn has_checkers_outside_last_quarter(&self) -> bool {
!self
.board
.get_color_fields(Color::White)
.iter()
.filter(|(field, _count)| *field < 19)
.collect::<Vec<&(usize, i8)>>()
.is_empty()
}
fn check_exit_rules(&self, moves: &(CheckerMove, CheckerMove)) -> Result<(), MoveError> {
if !moves.0.is_exit() && !moves.1.is_exit() {
return Ok(());
}
// toutes les dames doivent être dans le jan de retour
if self.has_checkers_outside_last_quarter() {
return Err(MoveError::ExitNeedsAllCheckersOnLastQuarter);
}
// toutes les sorties directes sont autorisées, ainsi que les nombres défaillants
let possible_moves_sequences = self.get_possible_moves_sequences(false);
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 Err(MoveError::ExitByEffectPossible);
}
// - la dame choisie doit être la plus éloignée de la sortie
let mut checkers = self.board.get_color_fields(Color::White);
checkers.sort_by(|a, b| b.0.cmp(&a.0));
let mut farthest = 24;
let mut next_farthest = 24;
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;
}
}
// s'il reste au moins deux dames, on vérifie que les plus éloignées soint choisies
if has_two_checkers {
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::ExitNotFasthest);
}
} 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::ExitNotFasthest);
}
}
}
}
Ok(())
}
2024-05-25 19:56:38 +02:00
pub fn get_possible_moves_sequences(
2024-05-21 21:22:04 +02:00
&self,
with_excedents: bool,
) -> Vec<(CheckerMove, CheckerMove)> {
2024-05-24 14:30:50 +02:00
let (dice1, dice2) = self.dice.values;
2024-05-23 21:11:02 +02:00
let (dice_max, dice_min) = if dice1 > dice2 {
(dice1, dice2)
} else {
(dice2, dice1)
};
2024-05-24 14:30:50 +02:00
let mut moves_seqs =
self.get_possible_moves_sequences_by_dices(dice_max, dice_min, with_excedents, false);
2024-05-23 17:37:45 +02:00
// if we got valid sequences whith the highest die, we don't accept sequences using only the
// lowest die
let ignore_empty = !moves_seqs.is_empty();
2024-05-23 21:11:02 +02:00
let mut moves_seqs_order2 = self.get_possible_moves_sequences_by_dices(
dice_min,
dice_max,
with_excedents,
ignore_empty,
);
2024-05-20 18:57:19 +02:00
moves_seqs.append(&mut moves_seqs_order2);
2024-05-21 21:22:04 +02:00
let empty_removed = moves_seqs
.iter()
.filter(|(c1, c2)| *c1 != EMPTY_MOVE && *c2 != EMPTY_MOVE);
if empty_removed.count() > 0 {
moves_seqs.retain(|(c1, c2)| *c1 != EMPTY_MOVE && *c2 != EMPTY_MOVE);
}
2024-05-20 18:57:19 +02:00
moves_seqs
}
2024-06-24 21:22:27 +02:00
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<Field> {
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
2024-05-25 19:56:38 +02:00
pub fn get_quarter_filling_moves_sequences(&self) -> Vec<(CheckerMove, CheckerMove)> {
2024-05-20 18:57:19 +02:00
let mut moves_seqs = Vec::new();
2024-05-24 14:30:50 +02:00
let color = &Color::White;
2024-06-24 21:22:27 +02:00
let all_moves_seqs = self.get_possible_moves_sequences(true);
2024-05-24 14:30:50 +02:00
for moves in self.get_possible_moves_sequences(true) {
let mut board = self.board.clone();
2024-05-20 18:57:19 +02:00
board.move_checker(color, moves.0).unwrap();
board.move_checker(color, moves.1).unwrap();
2024-06-24 21:22:27 +02:00
// println!("get_quarter_filling_moves_sequences board : {:?}", board);
if board.any_quarter_filled(*color) && !moves_seqs.contains(&moves) {
2024-05-20 18:57:19 +02:00
moves_seqs.push(moves);
}
}
moves_seqs
}
fn get_possible_moves_sequences_by_dices(
&self,
dice1: u8,
dice2: u8,
2024-05-21 21:22:04 +02:00
with_excedents: bool,
2024-05-23 17:37:45 +02:00
ignore_empty: bool,
2024-05-20 18:57:19 +02:00
) -> Vec<(CheckerMove, CheckerMove)> {
let mut moves_seqs = Vec::new();
2024-05-24 14:30:50 +02:00
let color = &Color::White;
2024-05-24 19:23:04 +02:00
let forbid_exits = self.has_checkers_outside_last_quarter();
for first_move in
self.board
.get_possible_moves(*color, dice1, with_excedents, false, forbid_exits)
2024-05-21 21:22:04 +02:00
{
2024-05-24 14:30:50 +02:00
let mut board2 = self.board.clone();
2024-05-20 18:57:19 +02:00
if board2.move_checker(color, first_move).is_err() {
println!("err move");
continue;
}
2024-05-21 21:22:04 +02:00
let mut has_second_dice_move = false;
2024-05-24 19:23:04 +02:00
for second_move in
board2.get_possible_moves(*color, dice2, with_excedents, true, forbid_exits)
{
2024-05-24 14:30:50 +02:00
if self.check_corner_rules(&(first_move, second_move)).is_ok() {
2024-05-23 21:11:02 +02:00
moves_seqs.push((first_move, second_move));
has_second_dice_move = true;
}
2024-05-21 21:22:04 +02:00
}
2024-05-24 14:30:50 +02:00
if !has_second_dice_move
&& with_excedents
&& !ignore_empty
&& self.check_corner_rules(&(first_move, EMPTY_MOVE)).is_ok()
{
// empty move
moves_seqs.push((first_move, EMPTY_MOVE));
2024-05-20 18:57:19 +02:00
}
2024-05-21 21:22:04 +02:00
//if board2.get_color_fields(*color).is_empty() {
2024-05-20 18:57:19 +02:00
}
moves_seqs
}
2024-05-24 14:30:50 +02:00
fn get_direct_exit_moves(&self, state: &GameState) -> Vec<CheckerMove> {
2024-05-20 18:57:19 +02:00
let mut moves = Vec::new();
let (dice1, dice2) = state.dice.values;
// sorties directes simples
2024-05-24 14:30:50 +02:00
let (field1_candidate, field2_candidate) = (25 - dice1 as usize, 25 - dice2 as usize);
2024-05-20 18:57:19 +02:00
let (count1, col1) = state.board.get_field_checkers(field1_candidate).unwrap();
let (count2, col2) = state.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
2024-05-24 14:30:50 +02:00
let fieldall_candidate = (25 - dice1 - dice2) as usize;
2024-05-20 18:57:19 +02:00
let (countall, _col) = state.board.get_field_checkers(fieldall_candidate).unwrap();
2024-05-24 14:30:50 +02:00
let color = &Color::White;
2024-05-20 18:57:19 +02:00
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
}
2024-05-25 19:56:38 +02:00
fn is_move_by_puissance(&self, moves: &(CheckerMove, CheckerMove)) -> bool {
2024-05-24 14:30:50 +02:00
let (dice1, dice2) = self.dice.values;
2024-05-25 19:56:38 +02:00
let dist1 = (moves.0.get_to() as i8 - moves.0.get_from() as i8).unsigned_abs();
let dist2 = (moves.1.get_to() as i8 - moves.1.get_from() as i8).unsigned_abs();
2024-05-20 18:57:19 +02:00
// Both corners must be empty
2024-05-24 14:30:50 +02:00
let (count1, _color) = self.board.get_field_checkers(12).unwrap();
let (count2, _color2) = self.board.get_field_checkers(13).unwrap();
2024-05-20 18:57:19 +02:00
if count1 > 0 || count2 > 0 {
return false;
}
2024-05-24 14:30:50 +02:00
let color = &Color::White;
2024-05-25 19:56:38 +02:00
moves.0.get_to() == moves.1.get_to()
&& moves.0.get_to() == self.board.get_color_corner(color)
2024-05-24 14:30:50 +02:00
&& (cmp::min(dist1, dist2) == cmp::min(dice1, dice2) - 1
2024-05-20 18:57:19 +02:00
&& cmp::max(dist1, dist2) == cmp::max(dice1, dice2) - 1)
}
2024-05-24 14:30:50 +02:00
fn can_take_corner_by_effect(&self) -> bool {
2024-05-20 18:57:19 +02:00
// return false if corner already taken
2024-05-24 14:30:50 +02:00
let color = &Color::White;
let corner_field: Field = self.board.get_color_corner(color);
let (count, _col) = self.board.get_field_checkers(corner_field).unwrap();
2024-05-20 18:57:19 +02:00
if count > 0 {
return false;
}
2024-05-24 14:30:50 +02:00
let (dice1, dice2) = self.dice.values;
let (field1, field2) = (corner_field - dice1 as usize, corner_field - dice2 as usize);
let res1 = self.board.get_field_checkers(field1);
let res2 = self.board.get_field_checkers(field2);
2024-05-20 18:57:19 +02:00
if res1.is_err() || res2.is_err() {
return false;
}
let (count1, opt_color1) = res1.unwrap();
let (count2, opt_color2) = res2.unwrap();
count1 > 0 && count2 > 0 && opt_color1 == Some(color) && opt_color2 == Some(color)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_take_corner_by_effect() {
2024-05-24 14:30:50 +02:00
let mut rules = MoveRules::default();
rules.board.set_positions([
2024-05-20 18:57:19 +02:00
10, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -15,
]);
2024-05-24 14:30:50 +02:00
rules.dice.values = (4, 4);
assert!(rules.can_take_corner_by_effect());
2024-05-20 18:57:19 +02:00
2024-05-24 14:30:50 +02:00
rules.dice.values = (5, 5);
assert!(!rules.can_take_corner_by_effect());
2024-05-20 18:57:19 +02:00
2024-05-24 14:30:50 +02:00
rules.board.set_positions([
2024-05-20 18:57:19 +02:00
10, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -15,
]);
2024-05-24 14:30:50 +02:00
rules.dice.values = (4, 4);
assert!(!rules.can_take_corner_by_effect());
2024-05-20 18:57:19 +02:00
}
#[test]
fn prise_en_puissance() {
2024-05-24 14:30:50 +02:00
let mut state = MoveRules::default();
2024-05-20 18:57:19 +02:00
// prise par puissance ok
state.board.set_positions([
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);
2024-05-25 19:56:38 +02:00
let moves = (
2024-05-20 18:57:19 +02:00
CheckerMove::new(8, 12).unwrap(),
CheckerMove::new(8, 12).unwrap(),
);
2024-05-25 19:56:38 +02:00
assert!(state.is_move_by_puissance(&moves));
assert!(state.moves_follows_dices(&moves));
assert!(state.moves_allowed(&moves).is_ok());
2024-05-20 18:57:19 +02:00
// opponent corner must be empty
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,
]);
2024-05-25 19:56:38 +02:00
assert!(!state.is_move_by_puissance(&moves));
assert!(!state.moves_follows_dices(&moves));
2024-05-20 18:57:19 +02:00
// 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([
5, 0, 0, 0, 0, 0, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -15,
]);
assert_eq!(
Err(MoveError::CornerByEffectPossible),
2024-05-25 19:56:38 +02:00
state.moves_allowed(&moves)
2024-05-20 18:57:19 +02:00
);
// on a déjà pris son coin : on ne peux plus y deplacer des dames par puissance
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,
]);
2024-05-25 19:56:38 +02:00
assert!(!state.is_move_by_puissance(&moves));
assert!(!state.moves_follows_dices(&moves));
2024-05-20 18:57:19 +02:00
}
#[test]
fn exit() {
2024-05-24 14:30:50 +02:00
let mut state = MoveRules::default();
2024-05-20 18:57:19 +02:00
// exit ok
state.board.set_positions([
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);
2024-05-25 19:56:38 +02:00
let moves = (
2024-05-20 18:57:19 +02:00
CheckerMove::new(20, 0).unwrap(),
CheckerMove::new(20, 0).unwrap(),
);
2024-05-25 19:56:38 +02:00
assert!(state.moves_follows_dices(&moves));
assert!(state.moves_allowed(&moves).is_ok());
2024-05-20 18:57:19 +02:00
// toutes les dames doivent être dans le jan de retour
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,
]);
state.dice.values = (5, 5);
2024-05-25 19:56:38 +02:00
let moves = (
2024-05-20 18:57:19 +02:00
CheckerMove::new(20, 0).unwrap(),
CheckerMove::new(20, 0).unwrap(),
);
assert_eq!(
Err(MoveError::ExitNeedsAllCheckersOnLastQuarter),
2024-05-25 19:56:38 +02:00
state.moves_allowed(&moves)
2024-05-20 18:57:19 +02:00
);
// 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);
2024-05-25 19:56:38 +02:00
let moves = (
2024-05-20 18:57:19 +02:00
CheckerMove::new(20, 0).unwrap(),
CheckerMove::new(23, 0).unwrap(),
);
2024-05-25 19:56:38 +02:00
assert_eq!(
Err(MoveError::ExitByEffectPossible),
state.moves_allowed(&moves)
);
2024-05-20 18:57:19 +02:00
// 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, 1, 1, 0, 1, 0,
]);
state.dice.values = (5, 5);
2024-05-25 19:56:38 +02:00
let moves = (
2024-05-20 18:57:19 +02:00
CheckerMove::new(20, 0).unwrap(),
CheckerMove::new(23, 0).unwrap(),
);
2024-05-25 19:56:38 +02:00
assert_eq!(Err(MoveError::ExitNotFasthest), state.moves_allowed(&moves));
let moves = (
2024-05-20 18:57:19 +02:00
CheckerMove::new(20, 0).unwrap(),
2024-05-21 21:22:04 +02:00
CheckerMove::new(21, 0).unwrap(),
2024-05-20 18:57:19 +02:00
);
2024-05-25 19:56:38 +02:00
assert!(state.moves_allowed(&moves).is_ok());
2024-05-20 18:57:19 +02:00
// 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);
2024-05-25 19:56:38 +02:00
let moves = (
2024-05-20 18:57:19 +02:00
CheckerMove::new(23, 0).unwrap(),
CheckerMove::new(0, 0).unwrap(),
);
2024-05-25 19:56:38 +02:00
assert!(state.moves_follows_dices(&moves));
assert!(state.moves_allowed(&moves).is_ok());
2024-05-20 18:57:19 +02:00
}
#[test]
fn move_check_opponent_fillable_quarter() {
2024-05-24 14:30:50 +02:00
let mut state = MoveRules::default();
2024-05-20 18:57:19 +02:00
state.board.set_positions([
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);
2024-05-25 19:56:38 +02:00
let moves = (
2024-05-20 18:57:19 +02:00
CheckerMove::new(11, 16).unwrap(),
CheckerMove::new(11, 16).unwrap(),
);
2024-05-25 19:56:38 +02:00
assert!(state.moves_allowed(&moves).is_ok());
2024-05-20 18:57:19 +02:00
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);
2024-05-25 19:56:38 +02:00
let moves = (
2024-05-20 18:57:19 +02:00
CheckerMove::new(11, 16).unwrap(),
CheckerMove::new(11, 16).unwrap(),
);
assert_eq!(
Err(MoveError::OpponentCanFillQuarter),
2024-05-25 19:56:38 +02:00
state.moves_allowed(&moves)
2024-05-20 18:57:19 +02:00
);
}
#[test]
fn move_check_fillable_quarter() {
2024-05-24 14:30:50 +02:00
let mut state = MoveRules::default();
2024-05-20 18:57:19 +02:00
state.board.set_positions([
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);
2024-05-25 19:56:38 +02:00
let moves = (
2024-05-20 18:57:19 +02:00
CheckerMove::new(1, 6).unwrap(),
CheckerMove::new(2, 6).unwrap(),
);
2024-05-25 19:56:38 +02:00
assert!(state.moves_allowed(&moves).is_ok());
let moves = (
2024-05-20 18:57:19 +02:00
CheckerMove::new(1, 5).unwrap(),
CheckerMove::new(2, 7).unwrap(),
);
2024-05-25 19:56:38 +02:00
assert_eq!(Err(MoveError::MustFillQuarter), state.moves_allowed(&moves));
2024-05-20 18:57:19 +02:00
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,
]);
state.dice.values = (2, 3);
2024-05-25 19:56:38 +02:00
let moves = (
2024-05-20 18:57:19 +02:00
CheckerMove::new(6, 8).unwrap(),
CheckerMove::new(6, 9).unwrap(),
);
2024-05-25 19:56:38 +02:00
assert_eq!(Err(MoveError::MustFillQuarter), state.moves_allowed(&moves));
let moves = (
2024-05-20 18:57:19 +02:00
CheckerMove::new(2, 4).unwrap(),
CheckerMove::new(5, 8).unwrap(),
);
2024-05-25 19:56:38 +02:00
assert!(state.moves_allowed(&moves).is_ok());
2024-05-20 18:57:19 +02:00
}
2024-05-21 21:22:04 +02:00
#[test]
fn move_play_all_dice() {
2024-05-24 14:30:50 +02:00
let mut state = MoveRules::default();
2024-05-21 21:22:04 +02:00
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, 1, 0, 0,
]);
state.dice.values = (1, 3);
2024-05-25 19:56:38 +02:00
let moves = (
2024-05-21 21:22:04 +02:00
CheckerMove::new(22, 0).unwrap(),
CheckerMove::new(0, 0).unwrap(),
);
2024-05-25 19:56:38 +02:00
assert_eq!(Err(MoveError::MustPlayAllDice), state.moves_allowed(&moves));
let moves = (
2024-05-21 21:22:04 +02:00
CheckerMove::new(22, 23).unwrap(),
CheckerMove::new(23, 0).unwrap(),
);
2024-05-25 19:56:38 +02:00
assert!(state.moves_allowed(&moves).is_ok());
2024-05-23 21:11:02 +02:00
}
#[test]
fn move_rest_corner_enter() {
// direct
2024-05-24 14:30:50 +02:00
let mut state = MoveRules::default();
2024-05-23 21:11:02 +02:00
state.board.set_positions([
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);
2024-05-25 19:56:38 +02:00
let moves = (
2024-05-23 21:11:02 +02:00
CheckerMove::new(10, 12).unwrap(),
CheckerMove::new(11, 12).unwrap(),
);
2024-05-25 19:56:38 +02:00
assert!(state.moves_follows_dices(&moves));
assert!(state.moves_allowed(&moves).is_ok());
2024-05-23 21:11:02 +02:00
// par puissance
state.dice.values = (3, 2);
2024-05-25 19:56:38 +02:00
let moves = (
2024-05-23 21:11:02 +02:00
CheckerMove::new(10, 12).unwrap(),
CheckerMove::new(11, 12).unwrap(),
);
2024-05-25 19:56:38 +02:00
assert!(state.moves_follows_dices(&moves));
assert!(state.moves_allowed(&moves).is_ok());
2024-05-23 21:11:02 +02:00
}
#[test]
fn move_rest_corner_blocked() {
2024-05-24 14:30:50 +02:00
let mut state = MoveRules::default();
2024-05-23 21:11:02 +02:00
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, 0, 0,
]);
state.dice.values = (2, 1);
2024-05-25 19:56:38 +02:00
let moves = (
2024-05-23 21:11:02 +02:00
CheckerMove::new(0, 0).unwrap(),
CheckerMove::new(0, 0).unwrap(),
);
2024-05-25 19:56:38 +02:00
assert!(state.moves_follows_dices(&moves));
assert!(state.moves_allowed(&moves).is_ok());
2024-05-23 21:11:02 +02:00
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,
]);
state.dice.values = (2, 1);
2024-05-25 19:56:38 +02:00
let moves = (
2024-05-23 21:11:02 +02:00
CheckerMove::new(23, 24).unwrap(),
CheckerMove::new(0, 0).unwrap(),
);
2024-05-25 19:56:38 +02:00
assert!(state.moves_follows_dices(&moves));
// let res = state.moves_allowed(&moves);
2024-05-24 19:23:04 +02:00
// println!("{:?}", res);
2024-05-25 19:56:38 +02:00
assert!(state.moves_allowed(&moves).is_ok());
2024-05-23 21:11:02 +02:00
2024-05-25 19:56:38 +02:00
let moves = (
2024-05-23 21:11:02 +02:00
CheckerMove::new(0, 0).unwrap(),
CheckerMove::new(0, 0).unwrap(),
);
2024-05-25 19:56:38 +02:00
assert_eq!(Err(MoveError::MustPlayAllDice), state.moves_allowed(&moves));
2024-05-21 21:22:04 +02:00
}
#[test]
fn move_rest_corner_exit() {
2024-05-24 14:30:50 +02:00
let mut state = MoveRules::default();
2024-05-21 21:22:04 +02:00
state.board.set_positions([
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);
2024-05-25 19:56:38 +02:00
let moves = (
2024-05-21 21:22:04 +02:00
CheckerMove::new(12, 14).unwrap(),
CheckerMove::new(1, 4).unwrap(),
);
assert_eq!(
Err(MoveError::CornerNeedsTwoCheckers),
2024-05-25 19:56:38 +02:00
state.moves_allowed(&moves)
2024-05-21 21:22:04 +02:00
);
}
#[test]
fn move_rest_corner_toutdune() {
let mut state = MoveRules::default();
// We can't go to the occupied rest corner as an intermediary step
state.board.set_positions([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
]);
state.dice.values = (2, 1);
let moves = (
CheckerMove::new(11, 13).unwrap(),
CheckerMove::new(13, 14).unwrap(),
);
assert!(!state.moves_possible(&moves));
// We can use the empty rest corner as an intermediary step
state.board.set_positions([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
]);
assert!(state.moves_possible(&moves));
assert!(state.moves_allowed(&moves).is_ok());
}
2024-05-21 21:22:04 +02:00
#[test]
fn move_play_stronger_dice() {
2024-05-24 14:30:50 +02:00
let mut state = MoveRules::default();
2024-05-21 21:22:04 +02:00
state.board.set_positions([
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);
2024-05-25 19:56:38 +02:00
let moves = (
2024-05-21 21:22:04 +02:00
CheckerMove::new(12, 14).unwrap(),
CheckerMove::new(0, 0).unwrap(),
);
2024-05-23 17:37:45 +02:00
// let poss = state.get_possible_moves_sequences(&Color::White, true);
// println!("{:?}", poss);
2024-05-25 19:56:38 +02:00
assert_eq!(
Err(MoveError::MustPlayStrongerDie),
state.moves_allowed(&moves)
);
let moves = (
2024-05-21 21:22:04 +02:00
CheckerMove::new(12, 15).unwrap(),
CheckerMove::new(0, 0).unwrap(),
);
2024-05-25 19:56:38 +02:00
assert!(state.moves_allowed(&moves).is_ok());
2024-05-21 21:22:04 +02:00
}
2024-05-20 18:57:19 +02:00
#[test]
fn moves_possible() {
2024-05-24 14:30:50 +02:00
let mut state = MoveRules::default();
2024-05-20 18:57:19 +02:00
// Chained moves
2024-05-25 19:56:38 +02:00
let moves = (
2024-05-20 18:57:19 +02:00
CheckerMove::new(1, 5).unwrap(),
CheckerMove::new(5, 9).unwrap(),
);
2024-05-25 19:56:38 +02:00
assert!(state.moves_possible(&moves));
2024-05-20 18:57:19 +02:00
// not chained moves
2024-05-25 19:56:38 +02:00
let moves = (
2024-05-20 18:57:19 +02:00
CheckerMove::new(1, 5).unwrap(),
CheckerMove::new(6, 9).unwrap(),
);
2024-05-25 19:56:38 +02:00
assert!(!state.moves_possible(&moves));
2024-05-20 18:57:19 +02:00
// black moves
2024-05-25 19:56:38 +02:00
let state = MoveRules::new(&Color::Black, &Board::default(), Dice::default());
let moves = (
CheckerMove::new(24, 20).unwrap().mirror(),
CheckerMove::new(20, 19).unwrap().mirror(),
);
assert!(state.moves_possible(&moves));
2024-05-20 18:57:19 +02:00
}
2024-06-24 21:22:27 +02:00
#[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());
}
// prise de coin par puissance et conservation de jan #18
// https://www.youtube.com/watch?v=5Bkxvd7MSps
#[test]
fn corner_by_effect_and_filled_corner() {
let mut state = MoveRules::default();
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,
]);
state.dice.values = (6, 5);
let moves = (
CheckerMove::new(7, 12).unwrap(),
CheckerMove::new(8, 12).unwrap(),
);
assert_eq!(
Err(MoveError::CornerByEffectPossible),
state.moves_allowed(&moves)
);
// on ne peut pas rompre car il y a un autre mouvement possible
let moves = (
CheckerMove::new(6, 12).unwrap(),
CheckerMove::new(7, 12).unwrap(),
);
assert_eq!(
Err(MoveError::MustFillQuarter),
state.moves_allowed(&moves)
);
// seul mouvement possible
let moves = (
CheckerMove::new(7, 13).unwrap(),
CheckerMove::new(13, 19).unwrap(),
);
assert!( state.moves_allowed(&moves).is_ok());
// s'il n'y a pas d'autre solution, on peut rompre
}
2024-05-20 18:57:19 +02:00
}