diff --git a/store/src/board.rs b/store/src/board.rs index 0fba2d6..de0e450 100644 --- a/store/src/board.rs +++ b/store/src/board.rs @@ -598,40 +598,12 @@ impl Board { core::array::from_fn(|i| i + min) } - /// Returns cumulative white-checker counts: result[i] = # white checkers in fields 1..=i. - /// result[0] = 0. - pub fn white_checker_cumulative(&self) -> [u8; 25] { - let mut cum = [0u8; 25]; - let mut total = 0u8; - for (i, &count) in self.positions.iter().enumerate() { - if count > 0 { - total += count as u8; - } - cum[i + 1] = total; - } - cum - } - pub fn move_checker(&mut self, color: &Color, cmove: CheckerMove) -> Result<(), Error> { self.remove_checker(color, cmove.from)?; self.add_checker(color, cmove.to)?; Ok(()) } - /// Reverse a previously applied `move_checker`. No validation: assumes the move was valid. - pub fn unmove_checker(&mut self, color: &Color, cmove: CheckerMove) { - let unit = match color { - Color::White => 1, - Color::Black => -1, - }; - if cmove.from != 0 { - self.positions[cmove.from - 1] += unit; - } - if cmove.to != 0 { - self.positions[cmove.to - 1] -= unit; - } - } - pub fn remove_checker(&mut self, color: &Color, field: Field) -> Result<(), Error> { if field == 0 { return Ok(()); diff --git a/store/src/game.rs b/store/src/game.rs index 0749040..d32734d 100644 --- a/store/src/game.rs +++ b/store/src/game.rs @@ -156,6 +156,13 @@ impl GameState { if let Some(p1) = self.players.get(&1) { mirrored_players.insert(2, p1.mirror()); } + let mirrored_history = self + .history + .clone() + .iter() + .map(|evt| evt.get_mirror(false)) + .collect(); + let (move1, move2) = self.dice_moves; GameState { stage: self.stage, @@ -164,7 +171,7 @@ impl GameState { active_player_id: mirrored_active_player, // active_player_id: self.active_player_id, players: mirrored_players, - history: Vec::new(), + history: mirrored_history, dice: self.dice, dice_points: self.dice_points, dice_moves: (move1.mirror(), move2.mirror()), diff --git a/store/src/game_rules_moves.rs b/store/src/game_rules_moves.rs index 396bcaf..955ab3c 100644 --- a/store/src/game_rules_moves.rs +++ b/store/src/game_rules_moves.rs @@ -220,7 +220,7 @@ impl MoveRules { // 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_exit_rules(moves).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) { @@ -238,7 +238,7 @@ impl MoveRules { // check exit rules // if !ignored_rules.contains(&TricTracRule::Exit) { - self.check_exit_rules(moves, None)?; + self.check_exit_rules(moves)?; // } // --- interdit de jouer dans un cadran que l'adversaire peut encore remplir ---- @@ -321,11 +321,7 @@ impl MoveRules { .is_empty() } - fn check_exit_rules( - &self, - moves: &(CheckerMove, CheckerMove), - exit_seqs: Option<&[(CheckerMove, CheckerMove)]>, - ) -> Result<(), MoveError> { + fn check_exit_rules(&self, moves: &(CheckerMove, CheckerMove)) -> Result<(), MoveError> { if !moves.0.is_exit() && !moves.1.is_exit() { return Ok(()); } @@ -335,22 +331,16 @@ impl MoveRules { } // 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) { + let ignored_rules = vec![TricTracRule::Exit]; + let possible_moves_sequences_without_excedent = + self.get_possible_moves_sequences(false, ignored_rules); + if possible_moves_sequences_without_excedent.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() { + if !possible_moves_sequences_without_excedent.is_empty() { return Err(MoveError::ExitByEffectPossible); } @@ -451,18 +441,12 @@ impl MoveRules { } 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(), + ignored_rules.clone(), ); // if we got valid sequences with the highest die, we don't accept sequences using only the // lowest die @@ -472,8 +456,7 @@ impl MoveRules { dice_max, with_excedents, ignore_empty, - &ignored_rules, - filling_seqs.as_deref(), + ignored_rules, ); moves_seqs.append(&mut moves_seqs_order2); let empty_removed = moves_seqs @@ -544,16 +527,14 @@ impl MoveRules { 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) { + let mut board = self.board.clone(); 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 } @@ -564,27 +545,18 @@ impl MoveRules { dice2: u8, with_excedents: bool, ignore_empty: bool, - ignored_rules: &[TricTracRule], - filling_seqs: Option<&[(CheckerMove, CheckerMove)]>, + ignored_rules: Vec, ) -> 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() { + let mut board2 = self.board.clone(); + if board2.move_checker(color, first_move).is_err() { println!("err move"); continue; } @@ -594,7 +566,7 @@ impl MoveRules { let mut has_second_dice_move = false; // println!(" ==== Second"); for second_move in - board.get_possible_moves(*color, dice2, with_excedents, true, forbid_exits) + board2.get_possible_moves(*color, dice2, with_excedents, true, forbid_exits) { if self .check_corner_rules(&(first_move, second_move)) @@ -618,10 +590,24 @@ impl MoveRules { && self.can_take_corner_by_effect()) && (ignored_rules.contains(&TricTracRule::Exit) || self - .check_exit_rules(&(first_move, second_move), exit_seqs.as_deref()) + .check_exit_rules(&(first_move, second_move)) + // .inspect_err(|e| { + // println!( + // " 2nd (exit rule): {:?} - {:?}, {:?}", + // e, first_move, second_move + // ) + // }) + .is_ok()) + && (ignored_rules.contains(&TricTracRule::MustFillQuarter) + || self + .check_must_fill_quarter_rule(&(first_move, second_move)) + // .inspect_err(|e| { + // println!( + // " 2nd: {:?} - {:?}, {:?} for {:?}", + // e, first_move, second_move, self.board + // ) + // }) .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 @@ -644,14 +630,16 @@ impl MoveRules { && !(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))) + || self.check_exit_rules(&(first_move, EMPTY_MOVE)).is_ok()) + && (ignored_rules.contains(&TricTracRule::MustFillQuarter) + || self + .check_must_fill_quarter_rule(&(first_move, EMPTY_MOVE)) + .is_ok()) { // empty move moves_seqs.push((first_move, EMPTY_MOVE)); } - board.unmove_checker(color, first_move); + //if board2.get_color_fields(*color).is_empty() { } moves_seqs } @@ -1510,7 +1498,6 @@ mod tests { 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( @@ -1518,8 +1505,7 @@ mod tests { state.dice.values.1, true, false, - &[], - filling_seqs.as_deref(), + vec![] ) ); @@ -1534,7 +1520,6 @@ mod tests { 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( @@ -1542,8 +1527,7 @@ mod tests { state.dice.values.1, true, false, - &[], - filling_seqs.as_deref(), + vec![] ) ); let moves = vec![( @@ -1557,8 +1541,7 @@ mod tests { state.dice.values.0, true, false, - &[], - filling_seqs.as_deref(), + vec![] ) ); @@ -1574,7 +1557,6 @@ mod tests { 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( @@ -1582,8 +1564,7 @@ mod tests { state.dice.values.1, true, false, - &[], - filling_seqs.as_deref(), + vec![] ) ); } @@ -1602,13 +1583,13 @@ mod tests { CheckerMove::new(19, 23).unwrap(), CheckerMove::new(22, 0).unwrap(), ); - assert!(state.check_exit_rules(&moves, None).is_ok()); + assert!(state.check_exit_rules(&moves).is_ok()); let moves = ( CheckerMove::new(19, 24).unwrap(), CheckerMove::new(22, 0).unwrap(), ); - assert!(state.check_exit_rules(&moves, None).is_ok()); + assert!(state.check_exit_rules(&moves).is_ok()); state.dice.values = (6, 4); state.board.set_positions( @@ -1621,7 +1602,7 @@ mod tests { CheckerMove::new(20, 24).unwrap(), CheckerMove::new(23, 0).unwrap(), ); - assert!(state.check_exit_rules(&moves, None).is_ok()); + assert!(state.check_exit_rules(&moves).is_ok()); } #[test] diff --git a/store/src/training_common.rs b/store/src/training_common.rs index 6a5b537..57094a9 100644 --- a/store/src/training_common.rs +++ b/store/src/training_common.rs @@ -3,6 +3,7 @@ use std::cmp::{max, min}; use std::fmt::{Debug, Display, Formatter}; +use crate::board::Board; use crate::{CheckerMove, Dice, GameEvent, GameState}; use serde::{Deserialize, Serialize}; @@ -220,11 +221,10 @@ pub fn get_valid_actions(game_state: &GameState) -> anyhow::Result anyhow::Result anyhow::Result anyhow::Result { - // Moves are always in White's coordinate system. For Black, mirror the board first. - let cum = if color == &crate::Color::Black { - state.board.mirror().white_checker_cumulative() + let dice = &state.dice; + let board = &state.board; + + if color == &crate::Color::Black { + // Moves are already 'white', so we don't mirror them + white_checker_moves_to_trictrac_action( + move1, + move2, + // &move1.clone().mirror(), + // &move2.clone().mirror(), + dice, + &board.clone().mirror(), + ) + // .map(|a| a.mirror()) } else { - state.board.white_checker_cumulative() - }; - white_checker_moves_to_trictrac_action(move1, move2, &state.dice, &cum) + white_checker_moves_to_trictrac_action(move1, move2, dice, board) + } } fn white_checker_moves_to_trictrac_action( move1: &CheckerMove, move2: &CheckerMove, dice: &Dice, - cum: &[u8; 25], + board: &Board, ) -> anyhow::Result { let to1 = move1.get_to(); let to2 = move2.get_to(); @@ -313,21 +321,11 @@ fn white_checker_moves_to_trictrac_action( } let dice_order = diff_move1 == dice.values.0 as usize; - // cum[i] = # white checkers in fields 1..=i (precomputed by the caller). - // checker1 is the ordinal of the last checker at from1. - let checker1 = cum[from1] as usize; - // checker2 is the ordinal on the board after move1 (removed from from1, added to to1). - // Adjust the cumulative in O(1) without cloning the board. - let checker2 = { - let mut c = cum[from2]; - if from1 > 0 && from2 >= from1 { - c -= 1; // one checker was removed from from1, shifting later ordinals down - } - if from1 > 0 && to1 > 0 && from2 >= to1 { - c += 1; // one checker was added at to1, shifting later ordinals up - } - c as usize - }; + let checker1 = board.get_field_checker(&crate::Color::White, from1) as usize; + let mut tmp_board = board.clone(); + // should not raise an error for a valid action + tmp_board.move_checker(&crate::Color::White, *move1)?; + let checker2 = tmp_board.get_field_checker(&crate::Color::White, from2) as usize; Ok(TrictracAction::Move { dice_order, checker1,