From d53b65c94796e6c4a911daacd61198a74f957a6f Mon Sep 17 00:00:00 2001 From: Henri Bourcereau Date: Sun, 15 Feb 2026 12:08:24 +0100 Subject: [PATCH] mirrors for open_spiel --- bot/src/lib.rs | 4 +-- store/src/game.rs | 49 ++++++++++++++++++++++++++--- store/src/game_rules_moves.rs | 15 ++++++++- store/src/game_rules_points.rs | 16 ++++++++++ store/src/player.rs | 10 ++++++ store/src/pyengine.rs | 43 ++++++++++++++++++------- store/src/training_common.rs | 57 +++++++++++++++++++++++++++------- 7 files changed, 164 insertions(+), 30 deletions(-) diff --git a/bot/src/lib.rs b/bot/src/lib.rs index 6db3418..8e68234 100644 --- a/bot/src/lib.rs +++ b/bot/src/lib.rs @@ -71,7 +71,7 @@ impl Bot { debug!(">>>> {:?} BOT handle", self.color); let game = self.strategy.get_mut_game(); let internal_event = if self.color == Color::Black { - &event.get_mirror() + &event.get_mirror(false) } else { event }; @@ -126,7 +126,7 @@ impl Bot { return if self.color == Color::Black { debug!(" bot (internal) evt : {internal_event:?} ; points : {player_points:?}"); debug!("<<<< end {:?} BOT handle", self.color); - internal_event.map(|evt| evt.get_mirror()) + internal_event.map(|evt| evt.get_mirror(false)) } else { debug!("<<<< end {:?} BOT handle", self.color); internal_event diff --git a/store/src/game.rs b/store/src/game.rs index 2bf1c64..b6f7603 100644 --- a/store/src/game.rs +++ b/store/src/game.rs @@ -2,13 +2,13 @@ use crate::board::{Board, CheckerMove}; use crate::dice::Dice; use crate::game_rules_moves::MoveRules; -use crate::game_rules_points::{PointsRules, PossibleJans}; +use crate::game_rules_points::{PointsRules, PossibleJans, PossibleJansMethods}; use crate::player::{Color, Player, PlayerId}; use log::{debug, error}; // use itertools::Itertools; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::hash::{Hash, Hasher}; use std::{fmt, str}; @@ -143,6 +143,40 @@ impl GameState { game } + pub fn mirror(&self) -> GameState { + let mirrored_active_player = if self.active_player_id == 1 { 2 } else { 1 }; + let mut mirrored_players = HashMap::new(); + if let Some(p2) = self.players.get(&2) { + mirrored_players.insert(1, p2.mirror()); + } + 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, + turn_stage: self.turn_stage, + board: self.board.mirror(), + active_player_id: mirrored_active_player, + // active_player_id: self.active_player_id, + players: mirrored_players, + history: mirrored_history, + dice: self.dice, + dice_points: self.dice_points, + dice_moves: (move1.mirror(), move2.mirror()), + dice_jans: self.dice_jans.mirror(), + roll_first: self.roll_first, + schools_enabled: self.schools_enabled, + } + } + fn set_schools_enabled(&mut self, schools_enabled: bool) { self.schools_enabled = schools_enabled; } @@ -436,10 +470,12 @@ impl GameState { Roll { player_id } => { // Check player exists if !self.players.contains_key(player_id) { + error!("unknown player_id"); return false; } // Check player is currently the one making their move if self.active_player_id != *player_id { + error!("not active player_id"); return false; } // Check the turn stage @@ -536,6 +572,7 @@ impl GameState { *moves }; if !rules.moves_follow_rules(&moves) { + // println!(">>> rules not followed "); error!("rules not followed "); return false; } @@ -555,7 +592,7 @@ impl GameState { pub fn init_player(&mut self, player_name: &str) -> Option { if self.players.len() > 2 { - println!("more than two players"); + // println!("more than two players"); return None; } @@ -869,10 +906,12 @@ impl GameEvent { } } - pub fn get_mirror(&self) -> Self { + pub fn get_mirror(&self, preserve_player: bool) -> Self { // let mut mirror = self.clone(); let mirror_player_id = if let Some(player_id) = self.player_id() { - if player_id == 1 { + if preserve_player { + player_id + } else if player_id == 1 { 2 } else { 1 diff --git a/store/src/game_rules_moves.rs b/store/src/game_rules_moves.rs index 31c43fa..625baec 100644 --- a/store/src/game_rules_moves.rs +++ b/store/src/game_rules_moves.rs @@ -63,6 +63,7 @@ impl MoveRules { fn get_board_from_color(color: &Color, board: &Board) -> Board { if *color == Color::Black { + println!("get_board_from_color -> mirror of {}", board); board.mirror() } else { board.clone() @@ -74,6 +75,7 @@ impl MoveRules { moves: &(CheckerMove, CheckerMove), // ignored_rules: Vec, ) -> bool { + println!("in moves_follow_rules"); // 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) @@ -81,7 +83,8 @@ impl MoveRules { let is_allowed = self.moves_allowed(moves); // let is_allowed = self.moves_allowed(moves, ignored_rules); if is_allowed.is_err() { - info!("Move not allowed : {:?}", is_allowed.unwrap_err()); + println!("Move not allowed : {:?}", is_allowed.unwrap_err()); + // info!("Move not allowed : {:?}", is_allowed.unwrap_err()); false } else { true @@ -99,6 +102,10 @@ impl MoveRules { 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; } @@ -110,6 +117,7 @@ impl MoveRules { 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; } @@ -117,6 +125,11 @@ impl MoveRules { || !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; } diff --git a/store/src/game_rules_points.rs b/store/src/game_rules_points.rs index 4e94d08..5899579 100644 --- a/store/src/game_rules_points.rs +++ b/store/src/game_rules_points.rs @@ -69,10 +69,26 @@ pub type PossibleJans = HashMap>; pub trait PossibleJansMethods { fn push(&mut self, jan: Jan, cmoves: (CheckerMove, CheckerMove)); fn merge(&mut self, other: Self); + fn mirror(&self) -> Self; // fn get_points(&self) -> u8; } impl PossibleJansMethods for PossibleJans { + fn mirror(&self) -> Self { + self.clone() + .into_iter() + .map(|(jan, moves)| { + ( + jan, + moves + .into_iter() + .map(|(m1, m2)| (m1.mirror(), m2.mirror())) + .collect(), + ) + }) + .collect() + } + fn push(&mut self, jan: Jan, cmoves: (CheckerMove, CheckerMove)) { if let Some(ways) = self.get_mut(&jan) { if !ways.contains(&cmoves) { diff --git a/store/src/player.rs b/store/src/player.rs index eeb5829..fa1e099 100644 --- a/store/src/player.rs +++ b/store/src/player.rs @@ -48,6 +48,16 @@ impl Player { } } + pub fn mirror(&self) -> Self { + let mut player = self.clone(); + player.color = if self.color == Color::White { + Color::Black + } else { + Color::White + }; + player + } + pub fn to_bits_string(&self) -> String { format!( "{:0>4b}{:0>4b}{:b}{:b}", diff --git a/store/src/pyengine.rs b/store/src/pyengine.rs index eed5d93..7fb00b2 100644 --- a/store/src/pyengine.rs +++ b/store/src/pyengine.rs @@ -50,9 +50,14 @@ impl TricTrac { self.game_state.active_player_id - 1 } - fn get_legal_actions(&self, player_id: u64) -> Vec { - if player_id == self.current_player_idx() { - get_valid_action_indices(&self.game_state) + fn get_legal_actions(&self, player_idx: u64) -> Vec { + if player_idx == self.current_player_idx() { + if player_idx == 0 { + get_valid_action_indices(&self.game_state) + } else { + let mirror = self.game_state.mirror(); + get_valid_action_indices(&mirror) + } } else { vec![] } @@ -80,14 +85,18 @@ impl TricTrac { } fn apply_action(&mut self, action_idx: usize) -> PyResult<()> { - if let Some(event) = - TrictracAction::from_action_index(action_idx).and_then(|a| a.to_event(&self.game_state)) - { - println!("get event {:?}", event); + if let Some(event) = TrictracAction::from_action_index(action_idx).and_then(|a| { + let needs_mirror = self.game_state.active_player_id == 2; + let game_state = if needs_mirror { + &self.game_state.mirror() + } else { + &self.game_state + }; + a.to_event(game_state) + .map(|e| if needs_mirror { e.get_mirror(false) } else { e }) + }) { if self.game_state.validate(&event) { - println!("valid event"); self.game_state.consume(&event); - println!("state {}", self.game_state); return Ok(()); } else { return Err(pyo3::exceptions::PyRuntimeError::new_err( @@ -113,8 +122,20 @@ impl TricTrac { [self.get_score(1), self.get_score(2)] } - fn get_tensor(&self, player: PlayerId) -> Vec { - self.game_state.to_vec() + fn get_tensor(&self, player_idx: u64) -> Vec { + if player_idx == 0 { + self.game_state.to_vec() + } else { + self.game_state.mirror().to_vec() + } + } + + fn get_observation_string(&self, player_idx: u64) -> String { + if player_idx == 0 { + format!("{}", self.game_state) + } else { + format!("{}", self.game_state.mirror()) + } } /// Afficher l'état du jeu (pour le débogage) diff --git a/store/src/training_common.rs b/store/src/training_common.rs index 9568bcb..86f03ae 100644 --- a/store/src/training_common.rs +++ b/store/src/training_common.rs @@ -3,7 +3,8 @@ use std::cmp::{max, min}; use std::fmt::{Debug, Display, Formatter}; -use crate::{CheckerMove, GameEvent, GameState}; +use crate::board::Board; +use crate::{CheckerMove, Dice, GameEvent, GameState}; use serde::{Deserialize, Serialize}; // 1 (Roll) + 1 (Go) + 512 (mouvements possibles) @@ -60,6 +61,22 @@ impl TrictracAction { } } + pub fn mirror(&self) -> TrictracAction { + match self { + TrictracAction::Roll => TrictracAction::Roll, + TrictracAction::Go => TrictracAction::Go, + TrictracAction::Move { + dice_order, + checker1, + checker2, + } => TrictracAction::Move { + dice_order: *dice_order, + checker1: if *checker1 == 0 { 0 } else { 25 - checker1 }, + checker2: if *checker2 == 0 { 0 } else { 25 - checker2 }, + }, + } + } + pub fn to_event(&self, state: &GameState) -> Option { match self { TrictracAction::Roll => { @@ -197,8 +214,6 @@ pub fn get_valid_actions(game_state: &GameState) -> Vec { let rules = crate::MoveRules::new(&color, &game_state.board, game_state.dice); let possible_moves = rules.get_possible_moves_sequences(true, vec![]); - // Modififier checker_moves_to_trictrac_action si on doit gérer Black - assert_eq!(color, crate::Color::White); for (move1, move2) in possible_moves { valid_actions.push(checker_moves_to_trictrac_action( &move1, &move2, &color, game_state, @@ -213,8 +228,6 @@ pub fn get_valid_actions(game_state: &GameState) -> Vec { possible_moves.push((CheckerMove::default(), CheckerMove::default())); } - // Modififier checker_moves_to_trictrac_action si on doit gérer Black - assert_eq!(color, crate::Color::White); for (move1, move2) in possible_moves { valid_actions.push(checker_moves_to_trictrac_action( &move1, &move2, &color, game_state, @@ -230,18 +243,40 @@ pub fn get_valid_actions(game_state: &GameState) -> Vec { valid_actions } -// Valid only for White player fn checker_moves_to_trictrac_action( move1: &CheckerMove, move2: &CheckerMove, color: &crate::Color, state: &GameState, +) -> TrictracAction { + let dice = &state.dice; + let board = &state.board; + + if color == &crate::Color::Black { + white_checker_moves_to_trictrac_action( + move1, + move2, + // &move1.clone().mirror(), + // &move2.clone().mirror(), + dice, + &board.clone().mirror(), + ) + .mirror() + } else { + white_checker_moves_to_trictrac_action(move1, move2, dice, board) + } +} + +fn white_checker_moves_to_trictrac_action( + move1: &CheckerMove, + move2: &CheckerMove, + dice: &Dice, + board: &Board, ) -> TrictracAction { let to1 = move1.get_to(); let to2 = move2.get_to(); let from1 = move1.get_from(); let from2 = move2.get_from(); - let dice = state.dice; let mut diff_move1 = if to1 > 0 { // Mouvement sans sortie @@ -277,14 +312,14 @@ fn checker_moves_to_trictrac_action( } let dice_order = diff_move1 == dice.values.0 as usize; - let checker1 = state.board.get_field_checker(color, from1) as usize; - let mut tmp_board = state.board.clone(); + 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 - let move_res = tmp_board.move_checker(color, *move1); + let move_res = tmp_board.move_checker(&crate::Color::White, *move1); if move_res.is_err() { panic!("error while moving checker {move_res:?}"); } - let checker2 = tmp_board.get_field_checker(color, from2) as usize; + let checker2 = tmp_board.get_field_checker(&crate::Color::White, from2) as usize; TrictracAction::Move { dice_order, checker1,