//! # 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 log::info;
use std::cmp;
use std::collections::HashSet;

#[derive(std::cmp::PartialEq, Debug)]
pub enum MoveError {
    // Opponent corner is forbidden
    OpponentCorner,
    // 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
    ExitNotFarthest,
    // Jeu dans un cadran que l'adversaire peut encore remplir
    OpponentCanFillQuarter,
    // remplir cadran si possible & conserver cadran rempli si possible ----
    MustFillQuarter,
    // 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,
}

#[derive(std::cmp::PartialEq, Debug, Clone)]
pub enum TricTracRule {
    Exit,
    MustFillQuarter,
    Corner,
}

/// 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
    pub fn new(color: &Color, board: &Board, dice: Dice) -> Self {
        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 {
            board.mirror()
        } else {
            board.clone()
        }
    }

    pub fn moves_follow_rules(
        &self,
        moves: &(CheckerMove, CheckerMove),
        // ignored_rules: Vec<TricTracRule>,
    ) -> bool {
        // 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)
        self.moves_possible(moves) && self.moves_follows_dices(moves) && {
            let is_allowed = self.moves_allowed(moves);
            // let is_allowed = self.moves_allowed(moves, ignored_rules);
            if is_allowed.is_err() {
                println!("Move not allowed : {:?}", is_allowed.unwrap_err());
                // info!("Move not allowed : {:?}", is_allowed.unwrap_err());
                false
            } else {
                true
            }
        }
        // && self.moves_allowed(moves).is_ok()
    }

    /// ---- moves_possibles : First of three checks for moves
    fn moves_possible(&self, moves: &(CheckerMove, CheckerMove)) -> bool {
        let color = &Color::White;

        let move0_from = moves.0.get_from();
        if 0 < move0_from && move0_from == moves.1.get_from() {
            if let Ok((field_count, Some(field_color))) = self.board.get_field_checkers(move0_from)
            {
                if color != field_color || field_count < 2 {
                    println!(
                        "Move not physically possible 1. field_color {:?}, count {}",
                        field_color, field_count
                    );
                    info!("Move not physically possible");
                    return false;
                }
            }
        }

        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)
            {
                println!("Tout d'une : Move not physically possible");
                info!("Tout d'une : Move not physically possible");
                return false;
            }
        } else if !self.board.move_possible(color, &moves.0)
            || !self.board.move_possible(color, &moves.1)
        {
            // Move is not physically possible
            println!("Move not physically possible 2");
            println!(
                "board: {}, color: {:?} move: {:?}",
                self.board, color, moves
            );
            info!("Move not physically possible");
            return false;
        }
        true
    }

    /// ----- moves_follows_dices : Second of three checks for moves
    fn moves_follows_dices(&self, moves: &(CheckerMove, CheckerMove)) -> bool {
        // Prise de coin par puissance
        if self.is_move_by_puissance(moves) {
            return true;
        }

        let (dice1, dice2) = self.dice.values;
        let (move1, move2): &(CheckerMove, CheckerMove) = moves;

        let move1_dices = self.get_move_compatible_dices(move1);
        if move1_dices.is_empty() {
            info!("Move does not follow dice");
            return false;
        }
        let move2_dices = self.get_move_compatible_dices(move2);
        if move2_dices.is_empty() {
            info!("Move does not follow dice");
            return false;
        }
        if move1_dices.len() == 1
            && move2_dices.len() == 1
            && move1_dices[0] == move2_dices[0]
            && dice1 != dice2
        {
            info!("Move does not follow dice");
            return false;
        }

        // no rule was broken
        true
    }

    fn get_move_compatible_dices(&self, cmove: &CheckerMove) -> Vec<u8> {
        let (dice1, dice2) = self.dice.values;

        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
            let min_dist = 25 - cmove.get_from();
            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
    }

    /// ---- moves_allowed : Third of three checks for moves
    pub fn moves_allowed(
        &self,
        moves: &(CheckerMove, CheckerMove),
        // ignored_rules: Vec<TricTracRule>,
    ) -> Result<(), MoveError> {
        self.check_corner_rules(moves)?;

        if self.is_move_by_puissance(moves) {
            if self.can_take_corner_by_effect() {
                return Err(MoveError::CornerByEffectPossible);
            } else {
                // subsequent rules cannot be broken whith a move by puissance
                return Ok(());
            }
        }

        // Si possible, les deux dés doivent être joués
        if moves.0.get_from() == 0 || moves.1.get_from() == 0 {
            let mut possible_moves_sequences = self.get_possible_moves_sequences(true, vec![]);
            possible_moves_sequences.retain(|moves| self.check_exit_rules(moves, None).is_ok());
            // possible_moves_sequences.retain(|moves| self.check_corner_rules(moves).is_ok());
            if !possible_moves_sequences.contains(moves) && !possible_moves_sequences.is_empty() {
                if *moves == (EMPTY_MOVE, EMPTY_MOVE) {
                    return Err(MoveError::MustPlayAllDice);
                }
                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);
                }
                return Err(MoveError::MustPlayStrongerDie);
            }
        }

        // check exit rules
        // if !ignored_rules.contains(&TricTracRule::Exit) {
        self.check_exit_rules(moves, None)?;
        // }

        // --- interdit de jouer dans un cadran que l'adversaire peut encore remplir ----
        self.check_opponent_can_fill_quarter_rule(moves)?;

        // --- remplir cadran si possible & conserver cadran rempli si possible ----
        // if !ignored_rules.contains(&TricTracRule::MustFillQuarter) {
        self.check_must_fill_quarter_rule(moves)?;
        // }
        // no rule was broken
        Ok(())
    }

    // --- interdit de jouer dans un cadran que l'adversaire peut encore remplir ----
    fn check_opponent_can_fill_quarter_rule(
        &self,
        moves: &(CheckerMove, CheckerMove),
    ) -> Result<(), MoveError> {
        // A chained move (tout d'une): the first destination is a resting field.
        // Exception: a resting field in the opponent's big jan (13-18) is allowed
        // during a chained move to pass into the return jan.
        let is_chained =
            moves.1.get_from() != 0 && moves.0.get_to() == moves.1.get_from();

        if !is_chained {
            let to0 = moves.0.get_to();
            if to0 > 12 && self.board.is_quarter_fillable(Color::Black, to0) {
                return Err(MoveError::OpponentCanFillQuarter);
            }
        }

        let to1 = moves.1.get_to();
        if to1 > 12 && self.board.is_quarter_fillable(Color::Black, to1) {
            return Err(MoveError::OpponentCanFillQuarter);
        }

        Ok(())
    }

    fn check_must_fill_quarter_rule(
        &self,
        moves: &(CheckerMove, CheckerMove),
    ) -> Result<(), MoveError> {
        let filling_moves_sequences = self.get_quarter_filling_moves_sequences();
        if !filling_moves_sequences.contains(moves) && !filling_moves_sequences.is_empty() {
            if *moves == (CheckerMove::default(), CheckerMove::default()) {
                println!("filling moves sequences: {:?}", filling_moves_sequences);
            }
            return Err(MoveError::MustFillQuarter);
        }
        Ok(())
    }

    fn check_corner_rules(&self, moves: &(CheckerMove, CheckerMove)) -> Result<(), MoveError> {
        let (from0, to0, from1, to1) = (
            moves.0.get_from(),
            moves.0.get_to(),
            moves.1.get_from(),
            moves.1.get_to(),
        );

        // Player corner
        let corner_field: Field = self.board.get_color_corner(&Color::White);
        let (corner_count, _color) = self.board.get_field_checkers(corner_field).unwrap();

        // 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
            && to0 != corner_field
            && to1 != corner_field
        {
            return Err(MoveError::CornerNeedsTwoCheckers);
        }

        // Oponnent corner
        let corner_field: Field = self.board.get_color_corner(&Color::Black);
        if to1 == corner_field || (to0 == corner_field && to0 != from1) {
            return Err(MoveError::OpponentCorner);
        }

        Ok(())
    }

    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),
        exit_seqs: Option<&[(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 owned;
        let seqs = match exit_seqs {
            Some(s) => s,
            None => {
                owned = self.get_possible_moves_sequences(false, vec![TricTracRule::Exit]);
                &owned
            }
        };
        if seqs.contains(moves) {
            return Ok(());
        }
        // À 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 sont possibles, on
        // refuse cette séquence
        if !seqs.is_empty() {
            return Err(MoveError::ExitByEffectPossible);
        }

        // 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
        // let board_to_check = if moves.0.get_to() == moves.1.get_from() {
        //     // Chained move: apply first move to get the board state
        //     let mut board_copy = self.board.clone();
        //     let _ = board_copy.move_checker(&Color::White, moves.0);
        //     board_copy
        // } else {
        //     self.board.clone()
        // };

        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);

        // dice normal order
        let (is_move1_exedant, is_move2_exedant) = self.move_excedants(moves, true);
        let is_not_farthest1 = (is_move1_exedant && moves.0.get_from() != farthest_on_move1)
            || (is_move2_exedant && moves.1.get_from() != farthest_on_move2);

        // dice reversed order
        let (is_move1_exedant, is_move2_exedant) = self.move_excedants(moves, false);
        let is_not_farthest2 = (is_move1_exedant && moves.0.get_from() != farthest_on_move1)
            || (is_move2_exedant && moves.1.get_from() != farthest_on_move2);

        if is_not_farthest1 && is_not_farthest2 {
            return Err(MoveError::ExitNotFarthest);
        }

        Ok(())
    }

    fn move_excedants(&self, moves: &(CheckerMove, CheckerMove), dice_order: bool) -> (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 (dice1, dice2) = if dice_order {
            self.dice.values
        } else {
            (self.dice.values.1, self.dice.values.0)
        };

        (
            dist1 != 0 && dist1 < dice1 as usize,
            dist2 != 0 && dist2 < dice2 as usize,
        )
    }

    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));

        let mut farthest = 25;
        // Check if we have a filled quarter that must be preserved
        let has_filled_quarter = board.any_quarter_filled(Color::White);
        if has_filled_quarter {
            // 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)
            let available_checkers: Vec<_> =
                checkers.iter().filter(|(_, count)| *count > 2).collect();
            if !available_checkers.is_empty() {
                // Use the farthest available checker (that won't break the quarter)
                farthest = available_checkers[0].0;
            }
        }
        if farthest == 25 {
            if let Some((field, _)) = checkers.first() {
                farthest = *field;
            }
        }
        farthest
    }

    pub fn get_possible_moves_sequences(
        &self,
        with_excedents: bool,
        ignored_rules: Vec<TricTracRule>,
    ) -> Vec<(CheckerMove, CheckerMove)> {
        let (dice1, dice2) = self.dice.values;
        let (dice_max, dice_min) = if dice1 > dice2 {
            (dice1, dice2)
        } else {
            (dice2, dice1)
        };
        let filling_seqs = if !ignored_rules.contains(&TricTracRule::MustFillQuarter) {
            Some(self.get_quarter_filling_moves_sequences())
        } else {
            None
        };
        let mut moves_seqs = self.get_possible_moves_sequences_by_dices(
            dice_max,
            dice_min,
            with_excedents,
            false,
            &ignored_rules,
            filling_seqs.as_deref(),
        );
        // 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(
            dice_min,
            dice_max,
            with_excedents,
            ignore_empty,
            &ignored_rules,
            filling_seqs.as_deref(),
        );
        moves_seqs.append(&mut moves_seqs_order2);
        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);
        }

        // deduplicate
        let mut set = HashSet::new();
        moves_seqs.retain(|x| set.insert(*x));

        moves_seqs
    }

    pub fn get_scoring_quarter_filling_moves_sequences(&self) -> Vec<(CheckerMove, CheckerMove)> {
        let all_seqs = self.get_quarter_filling_moves_sequences();
        if all_seqs.is_empty() {
            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
    pub fn get_quarter_filling_moves_sequences(&self) -> Vec<(CheckerMove, CheckerMove)> {
        let mut moves_seqs = Vec::new();
        let color = &Color::White;
        let ignored_rules = vec![TricTracRule::Exit, TricTracRule::MustFillQuarter];
        let mut board = self.board.clone();
        for moves in self.get_possible_moves_sequences(true, ignored_rules) {
            board.move_checker(color, moves.0).unwrap();
            board.move_checker(color, moves.1).unwrap();
            // println!("get_quarter_filling_moves_sequences board : {:?}", board);
            if board.any_quarter_filled(*color) && !moves_seqs.contains(&moves) {
                moves_seqs.push(moves);
            }
            board.unmove_checker(color, moves.1);
            board.unmove_checker(color, moves.0);
        }
        moves_seqs
    }

    fn get_possible_moves_sequences_by_dices(
        &self,
        dice1: u8,
        dice2: u8,
        with_excedents: bool,
        ignore_empty: bool,
        ignored_rules: &[TricTracRule],
        filling_seqs: Option<&[(CheckerMove, CheckerMove)]>,
    ) -> Vec<(CheckerMove, CheckerMove)> {
        let mut moves_seqs = Vec::new();
        let color = &Color::White;
        let forbid_exits = self.has_checkers_outside_last_quarter();
        // Precompute non-excedant sequences once so check_exit_rules need not repeat
        // the full move generation for every exit-move candidate.
        // Only needed when Exit is not already ignored and exits are actually reachable.
        let exit_seqs = if !ignored_rules.contains(&TricTracRule::Exit) && !forbid_exits {
            Some(self.get_possible_moves_sequences(false, vec![TricTracRule::Exit]))
        } else {
            None
        };
        let mut board = self.board.clone();
        // println!("==== First");
        for first_move in
            self.board
                .get_possible_moves(*color, dice1, with_excedents, false, forbid_exits)
        {
            if board.move_checker(color, first_move).is_err() {
                println!("err move");
                continue;
            }

            // XXX : the goal here is to replicate moves_allowed() checks without using get_possible_moves_sequences to
            // avoid an infinite loop...
            let mut has_second_dice_move = false;
            // println!("     ==== Second");
            for second_move in
                board.get_possible_moves(*color, dice2, with_excedents, true, forbid_exits)
            {
                if self
                    .check_corner_rules(&(first_move, second_move))
                    // .inspect_err(|e| {
                    //     println!(
                    //         " 2nd (corner rule): {:?} - {:?}, {:?}",
                    //         e, first_move, second_move
                    //     )
                    // })
                    .is_ok()
                    && self
                        .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()
                    && !(self.is_move_by_puissance(&(first_move, second_move))
                        && self.can_take_corner_by_effect())
                    && (ignored_rules.contains(&TricTracRule::Exit)
                        || self
                            .check_exit_rules(&(first_move, second_move), exit_seqs.as_deref())
                            .is_ok())
                    && filling_seqs.map_or(true, |seqs| {
                        seqs.is_empty() || seqs.contains(&(first_move, second_move))
                    })
                {
                    if second_move.get_to() == 0
                        && first_move.get_to() == 0
                        && second_move.get_from() < first_move.get_from()
                    {
                        moves_seqs.push((second_move, first_move));
                    } else {
                        moves_seqs.push((first_move, second_move));
                    }
                    has_second_dice_move = true;
                }
            }
            if !has_second_dice_move
                && with_excedents
                && !ignore_empty
                && self.check_corner_rules(&(first_move, EMPTY_MOVE)).is_ok()
                && self
                    .check_opponent_can_fill_quarter_rule(&(first_move, EMPTY_MOVE))
                    .is_ok()
                && !(self.is_move_by_puissance(&(first_move, EMPTY_MOVE))
                    && self.can_take_corner_by_effect())
                && (ignored_rules.contains(&TricTracRule::Exit)
                    || self
                        .check_exit_rules(&(first_move, EMPTY_MOVE), exit_seqs.as_deref())
                        .is_ok())
                && filling_seqs.map_or(true, |seqs| {
                    seqs.is_empty() || seqs.contains(&(first_move, EMPTY_MOVE))
                })
            {
                // empty move
                moves_seqs.push((first_move, EMPTY_MOVE));
            }
            board.unmove_checker(color, first_move);
        }
        moves_seqs
    }

    fn _get_direct_exit_moves(&self, state: &GameState) -> Vec<CheckerMove> {
        let mut moves = Vec::new();
        let (dice1, dice2) = state.dice.values;

        // sorties directes simples
        let (field1_candidate, field2_candidate) = (25 - dice1 as usize, 25 - dice2 as usize);
        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
        let fieldall_candidate = (25 - dice1 - dice2) as usize;
        let (countall, _col) = state.board.get_field_checkers(fieldall_candidate).unwrap();
        let color = &Color::White;
        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, moves: &(CheckerMove, CheckerMove)) -> bool {
        let (dice1, dice2) = self.dice.values;
        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();

        // Both corners must be empty
        let (count1, _color) = self.board.get_field_checkers(12).unwrap();
        let (count2, _color2) = self.board.get_field_checkers(13).unwrap();
        if count1 > 0 || count2 > 0 {
            return false;
        }

        let color = &Color::White;
        moves.0.get_to() == moves.1.get_to()
            && moves.0.get_to() == self.board.get_color_corner(color)
            && (cmp::min(dist1, dist2) == cmp::min(dice1, dice2) - 1
                && cmp::max(dist1, dist2) == cmp::max(dice1, dice2) - 1)
    }

    fn can_take_corner_by_effect(&self) -> bool {
        // return false if corner already taken
        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();
        if count > 0 {
            return false;
        }

        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);
        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() {
        let mut rules = MoveRules::default();
        rules.board.set_positions(
            &Color::White,
            [
                10, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -15,
            ],
        );
        rules.dice.values = (4, 4);
        assert!(rules.can_take_corner_by_effect());

        rules.dice.values = (5, 5);
        assert!(!rules.can_take_corner_by_effect());

        rules.board.set_positions(
            &Color::White,
            [
                10, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -15,
            ],
        );
        rules.dice.values = (4, 4);
        assert!(!rules.can_take_corner_by_effect());
    }

    #[test]
    fn prise_en_puissance() {
        let mut state = MoveRules::default();
        // prise par puissance ok
        state.board.set_positions(
            &Color::White,
            [
                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);
        let moves = (
            CheckerMove::new(8, 12).unwrap(),
            CheckerMove::new(8, 12).unwrap(),
        );
        assert!(state.is_move_by_puissance(&moves));
        assert!(state.moves_follows_dices(&moves));
        assert!(state.moves_allowed(&moves).is_ok());

        // opponent corner must be empty
        state.board.set_positions(
            &Color::White,
            [
                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(&moves));
        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
        state.board.set_positions(
            &Color::White,
            [
                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),
            state.moves_allowed(&moves)
        );

        // on a déjà pris son coin : on ne peux plus y deplacer des dames par puissance
        state.board.set_positions(
            &Color::White,
            [
                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(&moves));
        assert!(!state.moves_follows_dices(&moves));
    }

    #[test]
    fn exit() {
        let mut state = MoveRules::default();
        // exit ok
        state.board.set_positions(
            &Color::White,
            [
                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);
        let moves = (
            CheckerMove::new(20, 0).unwrap(),
            CheckerMove::new(20, 0).unwrap(),
        );
        assert!(state.moves_follows_dices(&moves));
        assert!(state.moves_allowed(&moves).is_ok());

        // toutes les dames doivent être dans le jan de retour
        state.board.set_positions(
            &Color::White,
            [
                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);
        let moves = (
            CheckerMove::new(20, 0).unwrap(),
            CheckerMove::new(20, 0).unwrap(),
        );
        assert_eq!(
            Err(MoveError::ExitNeedsAllCheckersOnLastQuarter),
            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
        state.board.set_positions(
            &Color::White,
            [
                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_eq!(
            Err(MoveError::ExitByEffectPossible),
            state.moves_allowed(&moves)
        );

        // on doit jouer le nombre excédant le plus éloigné
        state.board.set_positions(
            &Color::White,
            [
                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);
        let moves = (
            CheckerMove::new(20, 0).unwrap(),
            CheckerMove::new(23, 0).unwrap(),
        );
        assert_eq!(Err(MoveError::ExitNotFarthest), state.moves_allowed(&moves));
        let moves = (
            CheckerMove::new(20, 0).unwrap(),
            CheckerMove::new(21, 0).unwrap(),
        );
        assert!(state.moves_allowed(&moves).is_ok());

        // Cas de la dernière dame
        state.board.set_positions(
            &Color::White,
            [
                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_follows_dices(&moves));
        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]
    fn move_check_opponent_fillable_quarter() {
        let mut state = MoveRules::default();
        state.board.set_positions(
            &Color::White,
            [
                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);
        let moves = (
            CheckerMove::new(11, 16).unwrap(),
            CheckerMove::new(11, 16).unwrap(),
        );
        assert!(state.moves_allowed(&moves).is_ok());

        state.board.set_positions(
            &Color::White,
            [
                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(11, 16).unwrap(),
        );
        assert_eq!(
            Err(MoveError::OpponentCanFillQuarter),
            state.moves_allowed(&moves)
        );

        state.board.set_positions(
            &Color::Black,
            [
                10, 0, 0, 0, -1, 0, 2, 0, 0, 0, 1, 2, 0, -1, -1, 0, 2, 0, 0, 0, 0, 0, 0, -10,
            ],
        );
        state.dice.values = (4, 1);
        let moves = (
            CheckerMove::new(15, 14).unwrap().mirror(),
            CheckerMove::new(14, 10).unwrap().mirror(),
        );
        assert_eq!(
            Err(MoveError::OpponentCanFillQuarter),
            state.moves_allowed(&moves)
        );

        state.board.set_positions(
            &Color::Black,
            [
                0, 0, 0, 0, -1, 1, 3, 0, 3, 4, 1, 3, 0, -2, -5, -2, -1, -4, 0, 0, 0, 0, 0, 0,
            ],
        );
        state.dice.values = (6, 2);
        let moves = (
            CheckerMove::new(14, 8).unwrap().mirror(),
            CheckerMove::new(5, 3).unwrap().mirror(),
        );
        assert_eq!(
            Err(MoveError::OpponentCanFillQuarter),
            state.moves_allowed(&moves)
        );

        state.board.set_positions(
            &Color::White,
            [
                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(
            &Color::White,
            [
                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]
    fn move_check_fillable_quarter() {
        let mut state = MoveRules::default();
        state.board.set_positions(
            &Color::White,
            [
                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);
        let moves = (
            CheckerMove::new(1, 6).unwrap(),
            CheckerMove::new(2, 6).unwrap(),
        );
        assert!(state.moves_allowed(&moves).is_ok());
        let moves = (
            CheckerMove::new(1, 5).unwrap(),
            CheckerMove::new(2, 7).unwrap(),
        );
        assert_eq!(Err(MoveError::MustFillQuarter), state.moves_allowed(&moves));

        state.board.set_positions(
            &Color::White,
            [
                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);
        let moves = (
            CheckerMove::new(6, 8).unwrap(),
            CheckerMove::new(6, 9).unwrap(),
        );
        assert_eq!(Err(MoveError::MustFillQuarter), state.moves_allowed(&moves));
        let moves = (
            CheckerMove::new(2, 4).unwrap(),
            CheckerMove::new(5, 8).unwrap(),
        );
        assert!(state.moves_allowed(&moves).is_ok());
    }

    #[test]
    fn move_play_all_dice() {
        let mut state = MoveRules::default();
        state.board.set_positions(
            &Color::White,
            [
                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);
        let moves = (
            CheckerMove::new(22, 0).unwrap(),
            CheckerMove::new(0, 0).unwrap(),
        );

        assert_eq!(Err(MoveError::MustPlayAllDice), state.moves_allowed(&moves));
        let moves = (
            CheckerMove::new(22, 23).unwrap(),
            CheckerMove::new(23, 0).unwrap(),
        );
        assert!(state.moves_allowed(&moves).is_ok());
    }

    #[test]
    fn move_opponent_rest_corner_rules() {
        // fill with 2 checkers : forbidden
        let mut state = MoveRules::default();
        state.board.set_positions(
            &Color::White,
            [
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            ],
        );
        state.dice.values = (1, 1);
        let moves = (
            CheckerMove::new(12, 13).unwrap(),
            CheckerMove::new(12, 13).unwrap(),
        );
        assert!(state.moves_follows_dices(&moves));
        assert!(state.moves_allowed(&moves).is_err());

        // repos lors d'un déplacement tout d'une
        let moves = (
            CheckerMove::new(12, 13).unwrap(),
            CheckerMove::new(13, 14).unwrap(),
        );
        assert!(state.moves_allowed(&moves).is_ok());

        // one checker : forbidden
        let moves = (
            CheckerMove::new(12, 13).unwrap(),
            CheckerMove::new(11, 12).unwrap(),
        );
        assert!(state.moves_allowed(&moves).is_err());
    }

    #[test]
    fn move_rest_corner_enter() {
        // direct
        let mut state = MoveRules::default();
        state.board.set_positions(
            &Color::White,
            [
                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);
        let moves = (
            CheckerMove::new(10, 12).unwrap(),
            CheckerMove::new(11, 12).unwrap(),
        );
        assert!(state.moves_follows_dices(&moves));
        assert!(state.moves_allowed(&moves).is_ok());

        // par puissance
        state.dice.values = (3, 2);
        let moves = (
            CheckerMove::new(10, 12).unwrap(),
            CheckerMove::new(11, 12).unwrap(),
        );
        assert!(state.moves_follows_dices(&moves));
        assert!(state.moves_allowed(&moves).is_ok());
    }

    #[test]
    fn move_rest_corner_blocked() {
        let mut state = MoveRules::default();
        state.board.set_positions(
            &Color::White,
            [
                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);
        let moves = (
            CheckerMove::new(0, 0).unwrap(),
            CheckerMove::new(0, 0).unwrap(),
        );
        assert!(state.moves_follows_dices(&moves));
        assert!(state.moves_allowed(&moves).is_ok());

        state.board.set_positions(
            &Color::White,
            [
                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);
        let moves = (
            CheckerMove::new(23, 24).unwrap(),
            CheckerMove::new(0, 0).unwrap(),
        );
        assert!(state.moves_follows_dices(&moves));
        // let res = state.moves_allowed(&moves);
        // println!("{:?}", res);
        assert!(state.moves_allowed(&moves).is_ok());

        let moves = (
            CheckerMove::new(0, 0).unwrap(),
            CheckerMove::new(0, 0).unwrap(),
        );
        assert_eq!(Err(MoveError::MustPlayAllDice), state.moves_allowed(&moves));
    }

    #[test]
    fn move_rest_corner_exit() {
        let mut state = MoveRules::default();
        state.board.set_positions(
            &Color::White,
            [
                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);
        let moves = (
            CheckerMove::new(12, 14).unwrap(),
            CheckerMove::new(1, 4).unwrap(),
        );
        assert_eq!(
            Err(MoveError::CornerNeedsTwoCheckers),
            state.moves_allowed(&moves)
        );
    }

    #[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(
            &Color::White,
            [
                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(
            &Color::White,
            [
                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(8, 13).unwrap(),
            CheckerMove::new(13, 19).unwrap(),
        );

        assert!(state.moves_possible(&moves));
        assert!(state.moves_allowed(&moves).is_ok());
    }

    #[test]
    fn move_play_stronger_dice() {
        let mut state = MoveRules::default();
        state.board.set_positions(
            &Color::White,
            [
                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);
        let moves = (
            CheckerMove::new(12, 14).unwrap(),
            CheckerMove::new(0, 0).unwrap(),
        );
        // let poss = state.get_possible_moves_sequences(&Color::White, true);
        // println!("{:?}", poss);
        assert_eq!(
            Err(MoveError::MustPlayStrongerDie),
            state.moves_allowed(&moves)
        );
        let moves = (
            CheckerMove::new(12, 15).unwrap(),
            CheckerMove::new(0, 0).unwrap(),
        );
        assert!(state.moves_allowed(&moves).is_ok());
    }

    #[test]
    fn moves_possible() {
        let mut state = MoveRules::default();

        // Chained moves
        let moves = (
            CheckerMove::new(1, 5).unwrap(),
            CheckerMove::new(5, 9).unwrap(),
        );
        assert!(state.moves_possible(&moves));

        // not chained moves
        let moves = (
            CheckerMove::new(1, 5).unwrap(),
            CheckerMove::new(6, 9).unwrap(),
        );
        assert!(!state.moves_possible(&moves));

        // Can't move the same checker twice
        state.board.set_positions(
            &Color::White,
            [
                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 moves = (
            CheckerMove::new(3, 5).unwrap(),
            CheckerMove::new(3, 4).unwrap(),
        );
        assert!(!state.moves_possible(&moves));

        // Chaned  moves: can't rest on a field occupied by one opponent's checker
        state.board.set_positions(
            &Color::White,
            [
                0, 0, 0, 0, 0, 0, 6, 2, 2, 2, 2, 2, -2, -6, -1, -3, -1, 0, -2, 0, 0, 0, 0, 0,
            ],
        );
        state.dice.values = (5, 5);
        let moves = (
            CheckerMove::new(10, 15).unwrap(),
            CheckerMove::new(15, 20).unwrap(),
        );
        assert!(!state.moves_possible(&moves));

        // black moves
        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));
    }

    #[test]
    fn filling_moves_sequences() {
        let mut state = MoveRules::default();
        state.board.set_positions(
            &Color::White,
            [
                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(
            &Color::White,
            [
                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(
            &Color::White,
            [
                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());

        // positions
        state.board.set_positions(
            &Color::White,
            [
                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 filling_moves_sequences = state.get_quarter_filling_moves_sequences();
        assert_eq!(1, filling_moves_sequences.len());
    }

    #[test]
    fn scoring_filling_moves_sequences() {
        let mut state = MoveRules::default();

        state.board.set_positions(
            &Color::White,
            [
                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(
            &Color::White,
            [
                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());

        // preserve filling
        state.board.set_positions(
            &Color::White,
            [
                2, 2, 2, 2, 2, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -3, -1, -2, -3, -5, 0, -1,
            ],
        );
        state.dice.values = (3, 1);
        assert_eq!(1, state.get_scoring_quarter_filling_moves_sequences().len());

        // preserve filling (black)
        let mut state = MoveRules::new(&Color::Black, &Board::default(), Dice::default());
        state.board.set_positions(
            &Color::Black,
            [
                1, 0, 5, 3, 2, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -4, -2, -2, -2, -2, -2,
            ],
        );
        state.dice.values = (3, 1);
        assert_eq!(1, state.get_scoring_quarter_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(
            &Color::White,
            [
                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 le plein 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(8, 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
    }

    #[test]
    fn get_possible_moves_sequences() {
        let mut state = MoveRules::default();

        state.board.set_positions(
            &Color::White,
            [
                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, vec![])
        );

        state.board.set_positions(
            &Color::White,
            [
                -8, -4, -1, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 3, 2, 2, 2,
            ],
        );
        state.dice.values = (1, 4);
        let moves = (
            CheckerMove::new(21, 22).unwrap(),
            CheckerMove::new(22, 0).unwrap(),
        );
        assert_eq!(
            vec![moves],
            state.get_possible_moves_sequences(true, vec![])
        );

        state.dice.values = (5, 3);
        state.board.set_positions(
            &crate::Color::White,
            [
                -8, -3, -1, -1, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3,
            ],
        );
        let moves = (
            CheckerMove::new(23, 0).unwrap(),
            CheckerMove::new(24, 0).unwrap(),
        );
        assert_eq!(
            vec![moves],
            state.get_possible_moves_sequences(true, vec![])
        );

        let mut board = Board::new();
        board.set_positions(
            &crate::Color::White,
            [
                -3, -3, -2, -2, -2, -2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 3, 8,
            ],
        );
        let state = MoveRules::new(&Color::Black, &board, Dice { values: (5, 3) });
        let moves = (
            // CheckerMove::new(2, 0).unwrap(),
            // CheckerMove::new(1, 0).unwrap(),
            CheckerMove::new(23, 0).unwrap(),
            CheckerMove::new(24, 0).unwrap(),
        );
        assert_eq!(
            vec![moves],
            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]
    fn get_possible_moves_sequences_by_dices() {
        let mut state = MoveRules::default();

        state.dice.values = (5, 3);
        state.board.set_positions(
            &crate::Color::White,
            [
                -8, -3, -1, -1, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3,
            ],
        );
        let moves = (
            CheckerMove::new(23, 0).unwrap(),
            CheckerMove::new(24, 0).unwrap(),
        );
        let filling_seqs = Some(state.get_quarter_filling_moves_sequences());
        assert_eq!(
            vec![moves],
            state.get_possible_moves_sequences_by_dices(
                state.dice.values.0,
                state.dice.values.1,
                true,
                false,
                &[],
                filling_seqs.as_deref(),
            )
        );

        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(),
        )];
        let filling_seqs = Some(state.get_quarter_filling_moves_sequences());
        assert_eq!(
            moves,
            state.get_possible_moves_sequences_by_dices(
                state.dice.values.0,
                state.dice.values.1,
                true,
                false,
                &[],
                filling_seqs.as_deref(),
            )
        );
        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,
                &[],
                filling_seqs.as_deref(),
            )
        );

        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(),
        );
        let filling_seqs = Some(state.get_quarter_filling_moves_sequences());
        assert_eq!(
            vec![moves],
            state.get_possible_moves_sequences_by_dices(
                state.dice.values.0,
                state.dice.values.1,
                true,
                false,
                &[],
                filling_seqs.as_deref(),
            )
        );
    }

    #[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, None).is_ok());

        let moves = (
            CheckerMove::new(19, 24).unwrap(),
            CheckerMove::new(22, 0).unwrap(),
        );
        assert!(state.check_exit_rules(&moves, None).is_ok());

        state.dice.values = (6, 4);
        state.board.set_positions(
            &crate::Color::White,
            [
                -4, -1, -2, -1, 0, 0, 0, -1, 0, 0, 0, 0, -5, -1, 0, 0, 0, 0, 2, 3, 2, 2, 5, 1,
            ],
        );
        let moves = (
            CheckerMove::new(20, 24).unwrap(),
            CheckerMove::new(23, 0).unwrap(),
        );
        assert!(state.check_exit_rules(&moves, None).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());
    }

    #[test]
    fn check_rest_on_rest_corner() {
        let mut state = MoveRules::default();
        state.dice.values = (4, 1);
        state.board.set_positions(
            &crate::Color::White,
            [
                0, 0, -1, -4, -2, -1, 0, -2, -2, 4, 3, 2, 0, -1, -2, 0, 0, 1, 0, 1, 1, 1, 0, 2,
            ],
        );
        let moves = (
            CheckerMove::new(11, 12).unwrap(),
            CheckerMove::new(12, 16).unwrap(),
        );
        state.moves_allowed(&moves).expect("moves_allowed failed");
    }
}
