Compare commits
8 commits
bd4c75228b
...
145ab7dcda
| Author | SHA1 | Date | |
|---|---|---|---|
| 145ab7dcda | |||
| f26808d798 | |||
| 43eb5bf18d | |||
| dfc485a47a | |||
| a239c02937 | |||
| 6beaa56202 | |||
| 45b9db61e3 | |||
| 44a5ba87b0 |
4 changed files with 124 additions and 82 deletions
|
|
@ -598,12 +598,40 @@ impl Board {
|
||||||
core::array::from_fn(|i| i + min)
|
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> {
|
pub fn move_checker(&mut self, color: &Color, cmove: CheckerMove) -> Result<(), Error> {
|
||||||
self.remove_checker(color, cmove.from)?;
|
self.remove_checker(color, cmove.from)?;
|
||||||
self.add_checker(color, cmove.to)?;
|
self.add_checker(color, cmove.to)?;
|
||||||
Ok(())
|
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> {
|
pub fn remove_checker(&mut self, color: &Color, field: Field) -> Result<(), Error> {
|
||||||
if field == 0 {
|
if field == 0 {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
|
||||||
|
|
@ -156,13 +156,6 @@ impl GameState {
|
||||||
if let Some(p1) = self.players.get(&1) {
|
if let Some(p1) = self.players.get(&1) {
|
||||||
mirrored_players.insert(2, p1.mirror());
|
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;
|
let (move1, move2) = self.dice_moves;
|
||||||
GameState {
|
GameState {
|
||||||
stage: self.stage,
|
stage: self.stage,
|
||||||
|
|
@ -171,7 +164,7 @@ impl GameState {
|
||||||
active_player_id: mirrored_active_player,
|
active_player_id: mirrored_active_player,
|
||||||
// active_player_id: self.active_player_id,
|
// active_player_id: self.active_player_id,
|
||||||
players: mirrored_players,
|
players: mirrored_players,
|
||||||
history: mirrored_history,
|
history: Vec::new(),
|
||||||
dice: self.dice,
|
dice: self.dice,
|
||||||
dice_points: self.dice_points,
|
dice_points: self.dice_points,
|
||||||
dice_moves: (move1.mirror(), move2.mirror()),
|
dice_moves: (move1.mirror(), move2.mirror()),
|
||||||
|
|
|
||||||
|
|
@ -220,7 +220,7 @@ impl MoveRules {
|
||||||
// Si possible, les deux dés doivent être joués
|
// Si possible, les deux dés doivent être joués
|
||||||
if moves.0.get_from() == 0 || moves.1.get_from() == 0 {
|
if moves.0.get_from() == 0 || moves.1.get_from() == 0 {
|
||||||
let mut possible_moves_sequences = self.get_possible_moves_sequences(true, vec![]);
|
let mut possible_moves_sequences = self.get_possible_moves_sequences(true, vec![]);
|
||||||
possible_moves_sequences.retain(|moves| self.check_exit_rules(moves).is_ok());
|
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());
|
// possible_moves_sequences.retain(|moves| self.check_corner_rules(moves).is_ok());
|
||||||
if !possible_moves_sequences.contains(moves) && !possible_moves_sequences.is_empty() {
|
if !possible_moves_sequences.contains(moves) && !possible_moves_sequences.is_empty() {
|
||||||
if *moves == (EMPTY_MOVE, EMPTY_MOVE) {
|
if *moves == (EMPTY_MOVE, EMPTY_MOVE) {
|
||||||
|
|
@ -238,7 +238,7 @@ impl MoveRules {
|
||||||
|
|
||||||
// check exit rules
|
// check exit rules
|
||||||
// if !ignored_rules.contains(&TricTracRule::Exit) {
|
// if !ignored_rules.contains(&TricTracRule::Exit) {
|
||||||
self.check_exit_rules(moves)?;
|
self.check_exit_rules(moves, None)?;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// --- interdit de jouer dans un cadran que l'adversaire peut encore remplir ----
|
// --- interdit de jouer dans un cadran que l'adversaire peut encore remplir ----
|
||||||
|
|
@ -321,7 +321,11 @@ impl MoveRules {
|
||||||
.is_empty()
|
.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_exit_rules(&self, moves: &(CheckerMove, CheckerMove)) -> Result<(), MoveError> {
|
fn check_exit_rules(
|
||||||
|
&self,
|
||||||
|
moves: &(CheckerMove, CheckerMove),
|
||||||
|
exit_seqs: Option<&[(CheckerMove, CheckerMove)]>,
|
||||||
|
) -> Result<(), MoveError> {
|
||||||
if !moves.0.is_exit() && !moves.1.is_exit() {
|
if !moves.0.is_exit() && !moves.1.is_exit() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
@ -331,16 +335,22 @@ impl MoveRules {
|
||||||
}
|
}
|
||||||
|
|
||||||
// toutes les sorties directes sont autorisées, ainsi que les nombres défaillants
|
// toutes les sorties directes sont autorisées, ainsi que les nombres défaillants
|
||||||
let ignored_rules = vec![TricTracRule::Exit];
|
let owned;
|
||||||
let possible_moves_sequences_without_excedent =
|
let seqs = match exit_seqs {
|
||||||
self.get_possible_moves_sequences(false, ignored_rules);
|
Some(s) => s,
|
||||||
if possible_moves_sequences_without_excedent.contains(moves) {
|
None => {
|
||||||
|
owned = self
|
||||||
|
.get_possible_moves_sequences(false, vec![TricTracRule::Exit]);
|
||||||
|
&owned
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if seqs.contains(moves) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
// À ce stade au moins un des déplacements concerne un nombre en excédant
|
// À 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
|
// - si d'autres séquences de mouvements sans nombre en excédant sont possibles, on
|
||||||
// refuse cette séquence
|
// refuse cette séquence
|
||||||
if !possible_moves_sequences_without_excedent.is_empty() {
|
if !seqs.is_empty() {
|
||||||
return Err(MoveError::ExitByEffectPossible);
|
return Err(MoveError::ExitByEffectPossible);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -441,12 +451,18 @@ impl MoveRules {
|
||||||
} else {
|
} else {
|
||||||
(dice2, dice1)
|
(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(
|
let mut moves_seqs = self.get_possible_moves_sequences_by_dices(
|
||||||
dice_max,
|
dice_max,
|
||||||
dice_min,
|
dice_min,
|
||||||
with_excedents,
|
with_excedents,
|
||||||
false,
|
false,
|
||||||
ignored_rules.clone(),
|
&ignored_rules,
|
||||||
|
filling_seqs.as_deref(),
|
||||||
);
|
);
|
||||||
// if we got valid sequences with the highest die, we don't accept sequences using only the
|
// if we got valid sequences with the highest die, we don't accept sequences using only the
|
||||||
// lowest die
|
// lowest die
|
||||||
|
|
@ -456,7 +472,8 @@ impl MoveRules {
|
||||||
dice_max,
|
dice_max,
|
||||||
with_excedents,
|
with_excedents,
|
||||||
ignore_empty,
|
ignore_empty,
|
||||||
ignored_rules,
|
&ignored_rules,
|
||||||
|
filling_seqs.as_deref(),
|
||||||
);
|
);
|
||||||
moves_seqs.append(&mut moves_seqs_order2);
|
moves_seqs.append(&mut moves_seqs_order2);
|
||||||
let empty_removed = moves_seqs
|
let empty_removed = moves_seqs
|
||||||
|
|
@ -527,14 +544,16 @@ impl MoveRules {
|
||||||
let mut moves_seqs = Vec::new();
|
let mut moves_seqs = Vec::new();
|
||||||
let color = &Color::White;
|
let color = &Color::White;
|
||||||
let ignored_rules = vec![TricTracRule::Exit, TricTracRule::MustFillQuarter];
|
let ignored_rules = vec![TricTracRule::Exit, TricTracRule::MustFillQuarter];
|
||||||
for moves in self.get_possible_moves_sequences(true, ignored_rules) {
|
|
||||||
let mut board = self.board.clone();
|
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.0).unwrap();
|
||||||
board.move_checker(color, moves.1).unwrap();
|
board.move_checker(color, moves.1).unwrap();
|
||||||
// println!("get_quarter_filling_moves_sequences board : {:?}", board);
|
// println!("get_quarter_filling_moves_sequences board : {:?}", board);
|
||||||
if board.any_quarter_filled(*color) && !moves_seqs.contains(&moves) {
|
if board.any_quarter_filled(*color) && !moves_seqs.contains(&moves) {
|
||||||
moves_seqs.push(moves);
|
moves_seqs.push(moves);
|
||||||
}
|
}
|
||||||
|
board.unmove_checker(color, moves.1);
|
||||||
|
board.unmove_checker(color, moves.0);
|
||||||
}
|
}
|
||||||
moves_seqs
|
moves_seqs
|
||||||
}
|
}
|
||||||
|
|
@ -545,18 +564,27 @@ impl MoveRules {
|
||||||
dice2: u8,
|
dice2: u8,
|
||||||
with_excedents: bool,
|
with_excedents: bool,
|
||||||
ignore_empty: bool,
|
ignore_empty: bool,
|
||||||
ignored_rules: Vec<TricTracRule>,
|
ignored_rules: &[TricTracRule],
|
||||||
|
filling_seqs: Option<&[(CheckerMove, CheckerMove)]>,
|
||||||
) -> Vec<(CheckerMove, CheckerMove)> {
|
) -> Vec<(CheckerMove, CheckerMove)> {
|
||||||
let mut moves_seqs = Vec::new();
|
let mut moves_seqs = Vec::new();
|
||||||
let color = &Color::White;
|
let color = &Color::White;
|
||||||
let forbid_exits = self.has_checkers_outside_last_quarter();
|
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");
|
// println!("==== First");
|
||||||
for first_move in
|
for first_move in
|
||||||
self.board
|
self.board
|
||||||
.get_possible_moves(*color, dice1, with_excedents, false, forbid_exits)
|
.get_possible_moves(*color, dice1, with_excedents, false, forbid_exits)
|
||||||
{
|
{
|
||||||
let mut board2 = self.board.clone();
|
if board.move_checker(color, first_move).is_err() {
|
||||||
if board2.move_checker(color, first_move).is_err() {
|
|
||||||
println!("err move");
|
println!("err move");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -566,7 +594,7 @@ impl MoveRules {
|
||||||
let mut has_second_dice_move = false;
|
let mut has_second_dice_move = false;
|
||||||
// println!(" ==== Second");
|
// println!(" ==== Second");
|
||||||
for second_move in
|
for second_move in
|
||||||
board2.get_possible_moves(*color, dice2, with_excedents, true, forbid_exits)
|
board.get_possible_moves(*color, dice2, with_excedents, true, forbid_exits)
|
||||||
{
|
{
|
||||||
if self
|
if self
|
||||||
.check_corner_rules(&(first_move, second_move))
|
.check_corner_rules(&(first_move, second_move))
|
||||||
|
|
@ -590,24 +618,10 @@ impl MoveRules {
|
||||||
&& self.can_take_corner_by_effect())
|
&& self.can_take_corner_by_effect())
|
||||||
&& (ignored_rules.contains(&TricTracRule::Exit)
|
&& (ignored_rules.contains(&TricTracRule::Exit)
|
||||||
|| self
|
|| self
|
||||||
.check_exit_rules(&(first_move, second_move))
|
.check_exit_rules(&(first_move, second_move), exit_seqs.as_deref())
|
||||||
// .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())
|
.is_ok())
|
||||||
|
&& filling_seqs
|
||||||
|
.map_or(true, |seqs| seqs.is_empty() || seqs.contains(&(first_move, second_move)))
|
||||||
{
|
{
|
||||||
if second_move.get_to() == 0
|
if second_move.get_to() == 0
|
||||||
&& first_move.get_to() == 0
|
&& first_move.get_to() == 0
|
||||||
|
|
@ -630,16 +644,14 @@ impl MoveRules {
|
||||||
&& !(self.is_move_by_puissance(&(first_move, EMPTY_MOVE))
|
&& !(self.is_move_by_puissance(&(first_move, EMPTY_MOVE))
|
||||||
&& self.can_take_corner_by_effect())
|
&& self.can_take_corner_by_effect())
|
||||||
&& (ignored_rules.contains(&TricTracRule::Exit)
|
&& (ignored_rules.contains(&TricTracRule::Exit)
|
||||||
|| self.check_exit_rules(&(first_move, EMPTY_MOVE)).is_ok())
|
|| self.check_exit_rules(&(first_move, EMPTY_MOVE), exit_seqs.as_deref()).is_ok())
|
||||||
&& (ignored_rules.contains(&TricTracRule::MustFillQuarter)
|
&& filling_seqs
|
||||||
|| self
|
.map_or(true, |seqs| seqs.is_empty() || seqs.contains(&(first_move, EMPTY_MOVE)))
|
||||||
.check_must_fill_quarter_rule(&(first_move, EMPTY_MOVE))
|
|
||||||
.is_ok())
|
|
||||||
{
|
{
|
||||||
// empty move
|
// empty move
|
||||||
moves_seqs.push((first_move, EMPTY_MOVE));
|
moves_seqs.push((first_move, EMPTY_MOVE));
|
||||||
}
|
}
|
||||||
//if board2.get_color_fields(*color).is_empty() {
|
board.unmove_checker(color, first_move);
|
||||||
}
|
}
|
||||||
moves_seqs
|
moves_seqs
|
||||||
}
|
}
|
||||||
|
|
@ -1498,6 +1510,7 @@ mod tests {
|
||||||
CheckerMove::new(23, 0).unwrap(),
|
CheckerMove::new(23, 0).unwrap(),
|
||||||
CheckerMove::new(24, 0).unwrap(),
|
CheckerMove::new(24, 0).unwrap(),
|
||||||
);
|
);
|
||||||
|
let filling_seqs = Some(state.get_quarter_filling_moves_sequences());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vec![moves],
|
vec![moves],
|
||||||
state.get_possible_moves_sequences_by_dices(
|
state.get_possible_moves_sequences_by_dices(
|
||||||
|
|
@ -1505,7 +1518,8 @@ mod tests {
|
||||||
state.dice.values.1,
|
state.dice.values.1,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
vec![]
|
&[],
|
||||||
|
filling_seqs.as_deref(),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1520,6 +1534,7 @@ mod tests {
|
||||||
CheckerMove::new(19, 23).unwrap(),
|
CheckerMove::new(19, 23).unwrap(),
|
||||||
CheckerMove::new(22, 0).unwrap(),
|
CheckerMove::new(22, 0).unwrap(),
|
||||||
)];
|
)];
|
||||||
|
let filling_seqs = Some(state.get_quarter_filling_moves_sequences());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
moves,
|
moves,
|
||||||
state.get_possible_moves_sequences_by_dices(
|
state.get_possible_moves_sequences_by_dices(
|
||||||
|
|
@ -1527,7 +1542,8 @@ mod tests {
|
||||||
state.dice.values.1,
|
state.dice.values.1,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
vec![]
|
&[],
|
||||||
|
filling_seqs.as_deref(),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
let moves = vec![(
|
let moves = vec![(
|
||||||
|
|
@ -1541,7 +1557,8 @@ mod tests {
|
||||||
state.dice.values.0,
|
state.dice.values.0,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
vec![]
|
&[],
|
||||||
|
filling_seqs.as_deref(),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1557,6 +1574,7 @@ mod tests {
|
||||||
CheckerMove::new(19, 21).unwrap(),
|
CheckerMove::new(19, 21).unwrap(),
|
||||||
CheckerMove::new(23, 0).unwrap(),
|
CheckerMove::new(23, 0).unwrap(),
|
||||||
);
|
);
|
||||||
|
let filling_seqs = Some(state.get_quarter_filling_moves_sequences());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vec![moves],
|
vec![moves],
|
||||||
state.get_possible_moves_sequences_by_dices(
|
state.get_possible_moves_sequences_by_dices(
|
||||||
|
|
@ -1564,7 +1582,8 @@ mod tests {
|
||||||
state.dice.values.1,
|
state.dice.values.1,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
vec![]
|
&[],
|
||||||
|
filling_seqs.as_deref(),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1583,13 +1602,13 @@ mod tests {
|
||||||
CheckerMove::new(19, 23).unwrap(),
|
CheckerMove::new(19, 23).unwrap(),
|
||||||
CheckerMove::new(22, 0).unwrap(),
|
CheckerMove::new(22, 0).unwrap(),
|
||||||
);
|
);
|
||||||
assert!(state.check_exit_rules(&moves).is_ok());
|
assert!(state.check_exit_rules(&moves, None).is_ok());
|
||||||
|
|
||||||
let moves = (
|
let moves = (
|
||||||
CheckerMove::new(19, 24).unwrap(),
|
CheckerMove::new(19, 24).unwrap(),
|
||||||
CheckerMove::new(22, 0).unwrap(),
|
CheckerMove::new(22, 0).unwrap(),
|
||||||
);
|
);
|
||||||
assert!(state.check_exit_rules(&moves).is_ok());
|
assert!(state.check_exit_rules(&moves, None).is_ok());
|
||||||
|
|
||||||
state.dice.values = (6, 4);
|
state.dice.values = (6, 4);
|
||||||
state.board.set_positions(
|
state.board.set_positions(
|
||||||
|
|
@ -1602,7 +1621,7 @@ mod tests {
|
||||||
CheckerMove::new(20, 24).unwrap(),
|
CheckerMove::new(20, 24).unwrap(),
|
||||||
CheckerMove::new(23, 0).unwrap(),
|
CheckerMove::new(23, 0).unwrap(),
|
||||||
);
|
);
|
||||||
assert!(state.check_exit_rules(&moves).is_ok());
|
assert!(state.check_exit_rules(&moves, None).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
use std::cmp::{max, min};
|
use std::cmp::{max, min};
|
||||||
use std::fmt::{Debug, Display, Formatter};
|
use std::fmt::{Debug, Display, Formatter};
|
||||||
|
|
||||||
use crate::board::Board;
|
|
||||||
use crate::{CheckerMove, Dice, GameEvent, GameState};
|
use crate::{CheckerMove, Dice, GameEvent, GameState};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
@ -221,10 +220,11 @@ pub fn get_valid_actions(game_state: &GameState) -> anyhow::Result<Vec<TrictracA
|
||||||
// Ajoute aussi les mouvements possibles
|
// Ajoute aussi les mouvements possibles
|
||||||
let rules = crate::MoveRules::new(&color, &game_state.board, game_state.dice);
|
let rules = crate::MoveRules::new(&color, &game_state.board, game_state.dice);
|
||||||
let possible_moves = rules.get_possible_moves_sequences(true, vec![]);
|
let possible_moves = rules.get_possible_moves_sequences(true, vec![]);
|
||||||
|
// rules.board is already White-perspective (mirrored if Black): compute cum once.
|
||||||
|
let cum = rules.board.white_checker_cumulative();
|
||||||
for (move1, move2) in possible_moves {
|
for (move1, move2) in possible_moves {
|
||||||
valid_actions.push(checker_moves_to_trictrac_action(
|
valid_actions.push(white_checker_moves_to_trictrac_action(
|
||||||
&move1, &move2, &color, game_state,
|
&move1, &move2, &game_state.dice, &cum,
|
||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -235,10 +235,11 @@ pub fn get_valid_actions(game_state: &GameState) -> anyhow::Result<Vec<TrictracA
|
||||||
// Empty move
|
// Empty move
|
||||||
possible_moves.push((CheckerMove::default(), CheckerMove::default()));
|
possible_moves.push((CheckerMove::default(), CheckerMove::default()));
|
||||||
}
|
}
|
||||||
|
// rules.board is already White-perspective (mirrored if Black): compute cum once.
|
||||||
|
let cum = rules.board.white_checker_cumulative();
|
||||||
for (move1, move2) in possible_moves {
|
for (move1, move2) in possible_moves {
|
||||||
valid_actions.push(checker_moves_to_trictrac_action(
|
valid_actions.push(white_checker_moves_to_trictrac_action(
|
||||||
&move1, &move2, &color, game_state,
|
&move1, &move2, &game_state.dice, &cum,
|
||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -251,36 +252,27 @@ pub fn get_valid_actions(game_state: &GameState) -> anyhow::Result<Vec<TrictracA
|
||||||
Ok(valid_actions)
|
Ok(valid_actions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
fn checker_moves_to_trictrac_action(
|
fn checker_moves_to_trictrac_action(
|
||||||
move1: &CheckerMove,
|
move1: &CheckerMove,
|
||||||
move2: &CheckerMove,
|
move2: &CheckerMove,
|
||||||
color: &crate::Color,
|
color: &crate::Color,
|
||||||
state: &GameState,
|
state: &GameState,
|
||||||
) -> anyhow::Result<TrictracAction> {
|
) -> anyhow::Result<TrictracAction> {
|
||||||
let dice = &state.dice;
|
// Moves are always in White's coordinate system. For Black, mirror the board first.
|
||||||
let board = &state.board;
|
let cum = if color == &crate::Color::Black {
|
||||||
|
state.board.mirror().white_checker_cumulative()
|
||||||
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 {
|
} else {
|
||||||
white_checker_moves_to_trictrac_action(move1, move2, dice, board)
|
state.board.white_checker_cumulative()
|
||||||
}
|
};
|
||||||
|
white_checker_moves_to_trictrac_action(move1, move2, &state.dice, &cum)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn white_checker_moves_to_trictrac_action(
|
fn white_checker_moves_to_trictrac_action(
|
||||||
move1: &CheckerMove,
|
move1: &CheckerMove,
|
||||||
move2: &CheckerMove,
|
move2: &CheckerMove,
|
||||||
dice: &Dice,
|
dice: &Dice,
|
||||||
board: &Board,
|
cum: &[u8; 25],
|
||||||
) -> anyhow::Result<TrictracAction> {
|
) -> anyhow::Result<TrictracAction> {
|
||||||
let to1 = move1.get_to();
|
let to1 = move1.get_to();
|
||||||
let to2 = move2.get_to();
|
let to2 = move2.get_to();
|
||||||
|
|
@ -321,11 +313,21 @@ fn white_checker_moves_to_trictrac_action(
|
||||||
}
|
}
|
||||||
let dice_order = diff_move1 == dice.values.0 as usize;
|
let dice_order = diff_move1 == dice.values.0 as usize;
|
||||||
|
|
||||||
let checker1 = board.get_field_checker(&crate::Color::White, from1) as usize;
|
// cum[i] = # white checkers in fields 1..=i (precomputed by the caller).
|
||||||
let mut tmp_board = board.clone();
|
// checker1 is the ordinal of the last checker at from1.
|
||||||
// should not raise an error for a valid action
|
let checker1 = cum[from1] as usize;
|
||||||
tmp_board.move_checker(&crate::Color::White, *move1)?;
|
// checker2 is the ordinal on the board after move1 (removed from from1, added to to1).
|
||||||
let checker2 = tmp_board.get_field_checker(&crate::Color::White, from2) as usize;
|
// 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
|
||||||
|
};
|
||||||
Ok(TrictracAction::Move {
|
Ok(TrictracAction::Move {
|
||||||
dice_order,
|
dice_order,
|
||||||
checker1,
|
checker1,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue