diff --git a/bot/src/burnrl/environment.rs b/bot/src/burnrl/environment.rs index 99f1f1f..4a7bdcb 100644 --- a/bot/src/burnrl/environment.rs +++ b/bot/src/burnrl/environment.rs @@ -114,7 +114,7 @@ impl Environment for TrictracEnvironment { let player2_id = 2; // Commencer la partie - game.consume(&GameEvent::BeginGame { goes_first: 1 }); + let _ = game.consume(&GameEvent::BeginGame { goes_first: 1 }); let current_state = TrictracState::from_game_state(&game); TrictracEnvironment { @@ -145,7 +145,7 @@ impl Environment for TrictracEnvironment { self.game.init_player("Opponent"); // Commencer la partie - self.game.consume(&GameEvent::BeginGame { goes_first: 1 }); + let _ = self.game.consume(&GameEvent::BeginGame { goes_first: 1 }); self.current_state = TrictracState::from_game_state(&self.game); self.episode_reward = 0.0; @@ -282,7 +282,7 @@ impl TrictracEnvironment { // Appliquer l'événement si valide if let Some(event) = action.to_event(&self.game) { if self.game.validate(&event) { - self.game.consume(&event); + let _ = self.game.consume(&event); // reward += REWARD_VALID_MOVE; // Simuler le résultat des dés après un Roll if matches!(action, TrictracAction::Roll) { @@ -295,7 +295,7 @@ impl TrictracEnvironment { }, }; if self.game.validate(&dice_event) { - self.game.consume(&dice_event); + let _ = self.game.consume(&dice_event); let (points, adv_points) = self.game.dice_points; reward += REWARD_RATIO * (points as f32 - adv_points as f32); if points > 0 { @@ -397,7 +397,7 @@ impl TrictracEnvironment { }; if self.game.validate(&event) { - self.game.consume(&event); + let _ = self.game.consume(&event); if calculate_points { let dice_roll_count = self .game diff --git a/bot/src/burnrl/environment_valid.rs b/bot/src/burnrl/environment_valid.rs index 2648831..eaf4223 100644 --- a/bot/src/burnrl/environment_valid.rs +++ b/bot/src/burnrl/environment_valid.rs @@ -109,7 +109,7 @@ impl Environment for TrictracEnvironment { let player2_id = 2; // Commencer la partie - game.consume(&GameEvent::BeginGame { goes_first: 1 }); + let _ = game.consume(&GameEvent::BeginGame { goes_first: 1 }); let current_state = TrictracState::from_game_state(&game); TrictracEnvironment { @@ -136,7 +136,7 @@ impl Environment for TrictracEnvironment { self.game.init_player("Opponent"); // Commencer la partie - self.game.consume(&GameEvent::BeginGame { goes_first: 1 }); + let _ = self.game.consume(&GameEvent::BeginGame { goes_first: 1 }); self.current_state = TrictracState::from_game_state(&self.game); self.episode_reward = 0.0; @@ -252,7 +252,7 @@ impl TrictracEnvironment { // Appliquer l'événement si valide if let Some(event) = action.to_event(&self.game) { if self.game.validate(&event) { - self.game.consume(&event); + let _ = self.game.consume(&event); // reward += REWARD_VALID_MOVE; // Simuler le résultat des dés après un Roll if matches!(action, TrictracAction::Roll) { @@ -265,7 +265,7 @@ impl TrictracEnvironment { }, }; if self.game.validate(&dice_event) { - self.game.consume(&dice_event); + let _ = self.game.consume(&dice_event); let (points, adv_points) = self.game.dice_points; reward += REWARD_RATIO * (points as f32 - adv_points as f32); if points > 0 { @@ -367,7 +367,7 @@ impl TrictracEnvironment { }; if self.game.validate(&event) { - self.game.consume(&event); + let _ = self.game.consume(&event); if calculate_points { let dice_roll_count = self .game diff --git a/bot/src/lib.rs b/bot/src/lib.rs index 8e68234..a9b04d5 100644 --- a/bot/src/lib.rs +++ b/bot/src/lib.rs @@ -78,7 +78,7 @@ impl Bot { let init_player_points = game.who_plays().map(|p| (p.points, p.holes)); let turn_stage = game.turn_stage; - game.consume(internal_event); + let _ = game.consume(internal_event); if game.stage == Stage::Ended { debug!("<<<< end {:?} BOT handle", self.color); return None; diff --git a/bot/src/trictrac_board.rs b/bot/src/trictrac_board.rs index ddbd6be..7fce992 100644 --- a/bot/src/trictrac_board.rs +++ b/bot/src/trictrac_board.rs @@ -56,7 +56,7 @@ impl BoardGameBoard for TrictracBoard { fn play(&mut self, mv: Self::Move) -> Result<(), PlayError> { self.check_can_play(mv)?; if let Some(evt) = mv.to_event(&self.0) { - self.0.consume(&evt); + let _ = self.0.consume(&evt); Ok(()) } else { Err(PlayError::UnavailableMove) diff --git a/client_cli/src/game_runner.rs b/client_cli/src/game_runner.rs index 75c78b3..e8a5248 100644 --- a/client_cli/src/game_runner.rs +++ b/client_cli/src/game_runner.rs @@ -1,5 +1,5 @@ -use trictrac_bot::{Bot, BotStrategy}; use log::{debug, error}; +use trictrac_bot::{Bot, BotStrategy}; use trictrac_store::{CheckerMove, DiceRoller, GameEvent, GameState, PlayerId, TurnStage}; // Application Game @@ -67,7 +67,7 @@ impl GameRunner { "--------------- new valid event {event:?} (stage {:?}) -----------", self.state.turn_stage ); - self.state.consume(event); + let _ = self.state.consume(event).inspect_err(|e| error!("{}", e)); debug!( " --> stage {:?} ; active player points {:?}", self.state.turn_stage, diff --git a/store/src/board.rs b/store/src/board.rs index d9f0fb5..f2a76b3 100644 --- a/store/src/board.rs +++ b/store/src/board.rs @@ -8,7 +8,7 @@ use std::fmt; pub type Field = usize; pub type FieldWithCount = (Field, i8); -#[derive(Debug, Copy, Clone, Serialize, PartialEq, Eq, Deserialize)] +#[derive(Debug, Copy, Clone, Serialize, PartialEq, Eq, Deserialize, Hash)] pub struct CheckerMove { from: Field, to: Field, @@ -439,6 +439,7 @@ impl Board { check_rest_corner_exit: bool, forbid_exits: bool, ) -> Vec { + // println!("------- board.get_possible_moves..."); let mut moves = Vec::new(); let get_dest = |from| { @@ -453,6 +454,7 @@ impl Board { } }; + let mut farthest_exit_move = 25; for (field, count) in self.get_color_fields(color) { // check rest corner exit if field == self.get_color_corner(&color) && count == 2 && check_rest_corner_exit { @@ -463,8 +465,11 @@ impl Board { continue; } if !(0..25).contains(&dest) { + // if with_excedants && !forbid_exits && field < farthest_exit_move && 2 < count { if with_excedants && !forbid_exits { dest = 0; + farthest_exit_move = field; + // println!("farthest is now {farthest_exit_move}"); } else { continue; } @@ -834,4 +839,27 @@ mod tests { assert_eq!(4, board.get_field_checker(&Color::White, 2)); assert_eq!(6, board.get_field_checker(&Color::White, 3)); } + + #[test] + fn get_possible_moves() { + let mut board = Board::new(); + board.set_positions( + &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 = vec![ + CheckerMove::new(19, 22).unwrap(), + CheckerMove::new(20, 23).unwrap(), + CheckerMove::new(21, 24).unwrap(), + CheckerMove::new(22, 0).unwrap(), + CheckerMove::new(23, 0).unwrap(), + CheckerMove::new(24, 0).unwrap(), + ]; + assert_eq!( + moves, + board.get_possible_moves(Color::White, 3, true, true, false,) + ); + } } diff --git a/store/src/game.rs b/store/src/game.rs index 3030d72..d32734d 100644 --- a/store/src/game.rs +++ b/store/src/game.rs @@ -9,7 +9,7 @@ use log::{debug, error}; // use itertools::Itertools; use serde::{Deserialize, Serialize}; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::hash::{Hash, Hasher}; use std::{fmt, str}; @@ -140,7 +140,9 @@ impl GameState { let mut game = Self::default(); if let Some(p1) = game.init_player(p1_name) { game.init_player(p2_name); - game.consume(&GameEvent::BeginGame { goes_first: p1 }); + let _ = game + .consume(&GameEvent::BeginGame { goes_first: p1 }) + .inspect_err(|e| error!("{}", e)); } game } @@ -327,7 +329,7 @@ impl GameState { let mut bit_count: u8 = 0; // helper to push a single bit - let mut push_bit = |bit: u8, output: &mut Vec, current: &mut u8, bit_count: &mut u8| { + let push_bit = |bit: u8, output: &mut Vec, current: &mut u8, bit_count: &mut u8| { *current = (*current << 1) | (bit & 1); *bit_count += 1; @@ -339,7 +341,7 @@ impl GameState { }; // helper to push a string of '0'/'1' - let mut push_bits_str = + let push_bits_str = |bits: &str, output: &mut Vec, current: &mut u8, bit_count: &mut u8| { for b in bits.bytes() { push_bit(b - b'0', output, current, bit_count); @@ -1120,7 +1122,7 @@ mod tests { let mut game_state = init_test_gamestate(TurnStage::MarkPoints); game_state.schools_enabled = true; let pid = game_state.active_player_id; - game_state.consume( + let _ = game_state.consume( &(GameEvent::Mark { player_id: pid, points: 13, @@ -1132,7 +1134,7 @@ mod tests { assert_eq!(game_state.turn_stage, TurnStage::HoldOrGoChoice); // Go - game_state.consume( + let _ = game_state.consume( &(GameEvent::Go { player_id: game_state.active_player_id, }), @@ -1146,7 +1148,7 @@ mod tests { let mut game_state = init_test_gamestate(TurnStage::MarkPoints); game_state.schools_enabled = true; let pid = game_state.active_player_id; - game_state.consume( + let _ = game_state.consume( &(GameEvent::Mark { player_id: pid, points: 13, @@ -1156,7 +1158,7 @@ mod tests { CheckerMove::new(1, 3).unwrap(), CheckerMove::new(1, 3).unwrap(), ); - game_state.consume( + let _ = game_state.consume( &(GameEvent::Move { player_id: game_state.active_player_id, moves, diff --git a/store/src/game_rules_moves.rs b/store/src/game_rules_moves.rs index 20fd33d..f42bafc 100644 --- a/store/src/game_rules_moves.rs +++ b/store/src/game_rules_moves.rs @@ -5,6 +5,7 @@ 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 { @@ -22,7 +23,7 @@ pub enum MoveError { // sans nombre en excédant est possible ExitByEffectPossible, // Sortie avec nombre en excédant d'une dame qui n'est pas la plus éloignée - ExitNotFasthest, + ExitNotFarthest, // Jeu dans un cadran que l'adversaire peut encore remplir OpponentCanFillQuarter, // remplir cadran si possible & conserver cadran rempli si possible ---- @@ -348,6 +349,7 @@ impl MoveRules { 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(); + println!("mv 352"); let _ = board_copy.move_checker(&Color::White, moves.0); board_copy } else { @@ -355,7 +357,7 @@ impl MoveRules { }; let mut checkers = board_to_check.get_color_fields(Color::White); - checkers.sort_by(|a, b| b.0.cmp(&a.0)); + checkers.sort_by(|a, b| a.0.cmp(&b.0)); // Check if we have a filled quarter that must be preserved let has_filled_quarter = board_to_check.any_quarter_filled(Color::White); @@ -367,7 +369,7 @@ impl MoveRules { 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 mut available_checkers: Vec<_> = + let available_checkers: Vec<_> = checkers.iter().filter(|(_, count)| *count > 2).collect(); if !available_checkers.is_empty() { @@ -413,7 +415,7 @@ impl MoveRules { if moves.0.get_to() == 0 && moves.1.get_to() == 0 { // Deux coups sortants en excédant if cmp::max(moves.0.get_from(), moves.1.get_from()) > next_farthest { - return Err(MoveError::ExitNotFasthest); + return Err(MoveError::ExitNotFarthest); } } else { // Un seul coup sortant en excédant le coup sortant doit concerner la plus éloignée du bord @@ -423,7 +425,7 @@ impl MoveRules { moves.1.get_from() }; if exit_move_field != farthest { - return Err(MoveError::ExitNotFasthest); + return Err(MoveError::ExitNotFarthest); } } } @@ -465,6 +467,11 @@ impl MoveRules { 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 } @@ -524,8 +531,11 @@ impl MoveRules { 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(); + println!("mv 534"); board.move_checker(color, moves.0).unwrap(); + println!("mv 536 {:?} {:?}", self.board, moves); board.move_checker(color, moves.1).unwrap(); + println!("done 536"); // println!("get_quarter_filling_moves_sequences board : {:?}", board); if board.any_quarter_filled(*color) && !moves_seqs.contains(&moves) { moves_seqs.push(moves); @@ -545,11 +555,13 @@ impl MoveRules { let mut moves_seqs = Vec::new(); let color = &Color::White; let forbid_exits = self.has_checkers_outside_last_quarter(); + // println!("==== First"); for first_move in self.board .get_possible_moves(*color, dice1, with_excedents, false, forbid_exits) { let mut board2 = self.board.clone(); + println!("mv 560"); if board2.move_checker(color, first_move).is_err() { println!("err move"); continue; @@ -558,6 +570,7 @@ impl MoveRules { // 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 board2.get_possible_moves(*color, dice2, with_excedents, true, forbid_exits) { @@ -572,9 +585,22 @@ impl MoveRules { && (ignored_rules.contains(&TricTracRule::MustFillQuarter) || self .check_must_fill_quarter_rule(&(first_move, second_move)) + // .inspect_err(|e| { + // println!( + // " 2nd (must fill quar): {:?} - {:?}, {:?}", + // e, first_move, second_move + // ) + // }) .is_ok()) { - moves_seqs.push((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; } } @@ -825,7 +851,7 @@ mod tests { CheckerMove::new(20, 0).unwrap(), CheckerMove::new(23, 0).unwrap(), ); - assert_eq!(Err(MoveError::ExitNotFasthest), state.moves_allowed(&moves)); + assert_eq!(Err(MoveError::ExitNotFarthest), state.moves_allowed(&moves)); let moves = ( CheckerMove::new(20, 0).unwrap(), CheckerMove::new(21, 0).unwrap(), @@ -1371,5 +1397,48 @@ mod tests { 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![]) + ); + } + + #[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(), + ); + assert_eq!( + vec![moves], + state.get_possible_moves_sequences_by_dices( + state.dice.values.0, + state.dice.values.1, + true, + false, + vec![] + ) + ); } } diff --git a/store/src/pyengine.rs b/store/src/pyengine.rs index 593fa00..b193987 100644 --- a/store/src/pyengine.rs +++ b/store/src/pyengine.rs @@ -22,7 +22,7 @@ impl TricTrac { game_state.init_player("player2"); // Commencer la partie avec le joueur 1 - game_state.consume(&GameEvent::BeginGame { goes_first: 1 }); + let _ = game_state.consume(&GameEvent::BeginGame { goes_first: 1 }); TricTrac { game_state } } @@ -69,7 +69,8 @@ impl TricTrac { } let dice = Dice { values: dices }; - self.game_state + let _ = self + .game_state .consume(&GameEvent::RollResult { player_id, dice }); Ok(()) } @@ -86,7 +87,7 @@ impl TricTrac { .map(|e| if needs_mirror { e.get_mirror(false) } else { e }) }) { if self.game_state.validate(&event) { - self.game_state.consume(&event); + let _ = self.game_state.consume(&event); return Ok(()); } else { return Err(pyo3::exceptions::PyRuntimeError::new_err( diff --git a/store/src/training_common.rs b/store/src/training_common.rs index c22da54..3f93ca5 100644 --- a/store/src/training_common.rs +++ b/store/src/training_common.rs @@ -124,6 +124,7 @@ impl TrictracAction { let checker_move1 = CheckerMove::new(from1, to1).unwrap_or_default(); let mut tmp_board = state.board.clone(); + println!("mv training_common 127"); let move_result = tmp_board.move_checker(color, checker_move1); if move_result.is_err() { None @@ -262,14 +263,14 @@ fn checker_moves_to_trictrac_action( if color == &crate::Color::Black { white_checker_moves_to_trictrac_action( - move1, - move2, - // &move1.clone().mirror(), - // &move2.clone().mirror(), + // move1, + // move2, + &move1.clone().mirror(), + &move2.clone().mirror(), dice, &board.clone().mirror(), ) - .map(|a| a.mirror()) + // .map(|a| a.mirror()) } else { white_checker_moves_to_trictrac_action(move1, move2, dice, board) } @@ -323,8 +324,10 @@ fn white_checker_moves_to_trictrac_action( 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 + println!("mv training_common 327"); tmp_board.move_checker(&crate::Color::White, *move1)?; let checker2 = tmp_board.get_field_checker(&crate::Color::White, from2) as usize; + println!("white action {checker1} {checker2}"); Ok(TrictracAction::Move { dice_order, checker1, @@ -379,7 +382,7 @@ mod tests { } #[test] - fn get_valid_actions_fillquarter() { + fn get_valid_actions() { let mut state = GameState::new_with_players("white", "black"); state.active_player_id = 2; state.dice = Dice { values: (5, 3) }; @@ -394,8 +397,64 @@ mod tests { let actions = vec![TrictracAction::Move { dice_order: true, checker1: 11, - checker2: 14, + checker2: 13, }]; - assert_eq!(Some(actions), get_valid_actions(&state).ok()); + assert_eq!(Some(actions), super::get_valid_actions(&state).ok()); + } + + #[test] + fn checker_moves_to_trictrac_action() { + let mut state = GameState::new_with_players("white", "black"); + state.turn_stage = crate::TurnStage::Move; + state.dice = Dice { values: (5, 3) }; + + // White player + state.active_player_id = 1; + 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 ttaction = super::checker_moves_to_trictrac_action( + &CheckerMove::new(23, 0).unwrap(), + &CheckerMove::new(24, 0).unwrap(), + &crate::Color::White, + &state, + ); + + assert_eq!( + Some(TrictracAction::Move { + dice_order: true, + checker1: 11, + checker2: 13, // because the 11th has left + }), + ttaction.ok() + ); + + // Black player + state.active_player_id = 2; + state.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 ttaction = super::checker_moves_to_trictrac_action( + &CheckerMove::new(2, 0).unwrap(), + &CheckerMove::new(1, 0).unwrap(), + &crate::Color::Black, + &state, + ); + + assert_eq!( + Some(TrictracAction::Move { + dice_order: true, + checker1: 11, + checker2: 13, // because the 11th has left + }), + ttaction.ok() + ); } }