From f556ae10b8e00b6d12be1f9e910eb9d16acd951c Mon Sep 17 00:00:00 2001 From: Henri Bourcereau Date: Sat, 4 Apr 2026 18:51:35 +0200 Subject: [PATCH] refact: remove python & c++ bindings --- Cargo.lock | 172 +--------------------------- bot/Cargo.toml | 2 +- client_cli/Cargo.toml | 2 +- devenv.nix | 52 --------- spiel_bot/Cargo.toml | 2 +- store/Cargo.toml | 17 +-- store/build.rs | 9 -- store/pyproject.toml | 8 -- store/src/cxxengine.rs | 252 ----------------------------------------- store/src/lib.rs | 8 -- store/src/player.rs | 3 - store/src/pyengine.rs | 146 ------------------------ 12 files changed, 5 insertions(+), 668 deletions(-) delete mode 100644 store/build.rs delete mode 100644 store/pyproject.toml delete mode 100644 store/src/cxxengine.rs delete mode 100644 store/src/pyengine.rs diff --git a/Cargo.lock b/Cargo.lock index 94e366c..b19ee85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1381,7 +1381,6 @@ checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstyle", "clap_lex", - "strsim", ] [[package]] @@ -1447,17 +1446,6 @@ dependencies = [ "unicode-width 0.2.0", ] -[[package]] -name = "codespan-reporting" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" -dependencies = [ - "serde", - "termcolor", - "unicode-width 0.2.0", -] - [[package]] name = "collection_literals" version = "1.0.3" @@ -2278,68 +2266,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "cxx" -version = "1.0.194" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747d8437319e3a2f43d93b341c137927ca70c0f5dabeea7a005a73665e247c7e" -dependencies = [ - "cc", - "cxx-build", - "cxxbridge-cmd", - "cxxbridge-flags", - "cxxbridge-macro", - "foldhash 0.2.0", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.194" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0f4697d190a142477b16aef7da8a99bfdc41e7e8b1687583c0d23a79c7afc1e" -dependencies = [ - "cc", - "codespan-reporting 0.13.1", - "indexmap", - "proc-macro2", - "quote", - "scratch", - "syn 2.0.114", -] - -[[package]] -name = "cxxbridge-cmd" -version = "1.0.194" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0956799fa8678d4c50eed028f2de1c0552ae183c76e976cf7ca8c4e36a7c328" -dependencies = [ - "clap", - "codespan-reporting 0.13.1", - "indexmap", - "proc-macro2", - "quote", - "syn 2.0.114", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.194" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23384a836ab4f0ad98ace7e3955ad2de39de42378ab487dc28d3990392cb283a" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.194" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6acc6b5822b9526adfb4fc377b67128fdd60aac757cc4a741a6278603f763cf" -dependencies = [ - "indexmap", - "proc-macro2", - "quote", - "syn 2.0.114", -] - [[package]] name = "darling" version = "0.20.11" @@ -4875,15 +4801,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfae20f6b19ad527b550c223fddc3077a547fc70cda94b9b566575423fd303ee" -[[package]] -name = "link-cplusplus" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82" -dependencies = [ - "cc", -] - [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -5073,15 +4990,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - [[package]] name = "merge" version = "0.1.0" @@ -5180,7 +5088,7 @@ dependencies = [ "bitflags 2.10.0", "cfg-if", "cfg_aliases", - "codespan-reporting 0.12.0", + "codespan-reporting", "half", "hashbrown 0.15.5", "hexf-parse", @@ -6045,69 +5953,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "pyo3" -version = "0.23.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7778bffd85cf38175ac1f545509665d0b9b92a198ca7941f131f85f7a4f9a872" -dependencies = [ - "cfg-if", - "indoc", - "libc", - "memoffset", - "once_cell", - "portable-atomic", - "pyo3-build-config", - "pyo3-ffi", - "pyo3-macros", - "unindent", -] - -[[package]] -name = "pyo3-build-config" -version = "0.23.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f6cbe86ef3bf18998d9df6e0f3fc1050a8c5efa409bf712e661a4366e010fb" -dependencies = [ - "once_cell", - "target-lexicon", -] - -[[package]] -name = "pyo3-ffi" -version = "0.23.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f1b4c431c0bb1c8fb0a338709859eed0d030ff6daa34368d3b152a63dfdd8d" -dependencies = [ - "libc", - "pyo3-build-config", -] - -[[package]] -name = "pyo3-macros" -version = "0.23.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc2201328f63c4710f68abdf653c89d8dbc2858b88c5d88b0ff38a75288a9da" -dependencies = [ - "proc-macro2", - "pyo3-macros-backend", - "quote", - "syn 2.0.114", -] - -[[package]] -name = "pyo3-macros-backend" -version = "0.23.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fca6726ad0f3da9c9de093d6f116a93c1a38e417ed73bf138472cf4064f72028" -dependencies = [ - "heck", - "proc-macro2", - "pyo3-build-config", - "quote", - "syn 2.0.114", -] - [[package]] name = "qoi" version = "0.4.1" @@ -6955,12 +6800,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "scratch" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2" - [[package]] name = "sdl2" version = "0.37.0" @@ -7626,12 +7465,6 @@ dependencies = [ "xattr", ] -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - [[package]] name = "tch" version = "0.22.0" @@ -8276,11 +8109,8 @@ version = "0.1.0" dependencies = [ "anyhow", "base64 0.21.7", - "cxx", - "cxx-build", "log", "merge", - "pyo3", "rand 0.9.2", "serde", "transpose", diff --git a/bot/Cargo.toml b/bot/Cargo.toml index d24adcc..de957df 100644 --- a/bot/Cargo.toml +++ b/bot/Cargo.toml @@ -13,7 +13,7 @@ path = "src/burnrl/main.rs" pretty_assertions = "1.4.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -trictrac-store = { path = "../store", features = ["python"] } +trictrac-store = { path = "../store" } rand = "0.9" env_logger = "0.10" burn = { version = "0.20", features = ["ndarray", "autodiff"] } diff --git a/client_cli/Cargo.toml b/client_cli/Cargo.toml index d85dd8b..52318cb 100644 --- a/client_cli/Cargo.toml +++ b/client_cli/Cargo.toml @@ -13,7 +13,7 @@ bincode = "1.3.3" pico-args = "0.5.0" pretty_assertions = "1.4.0" renet = "0.0.13" -trictrac-store = { path = "../store", features = ["python"] } +trictrac-store = { path = "../store" } trictrac-bot = { path = "../bot" } spiel_bot = { path = "../spiel_bot" } itertools = "0.13.0" diff --git a/devenv.nix b/devenv.nix index c8cc20c..29582d6 100644 --- a/devenv.nix +++ b/devenv.nix @@ -21,63 +21,11 @@ in pkgs.samply # code profiler pkgs.feedgnuplot # to visualize bots training results - # --- AI training with python --- - # generate python classes from rust code - pkgs.maturin - # required by python numpy - pkgs.libz - - # for bevy - pkgs.alsa-lib - pkgs.udev - - # bevy fast compile - pkgs.clang - pkgs.lld - - # copié de https://github.com/mmai/Hyperspeedcube/blob/develop/devenv.nix - # TODO : retirer ce qui est inutile - # pour erreur à l'exécution, selon https://github.com/emilk/egui/discussions/1587 - pkgs.libxkbcommon - pkgs.libGL - - # WINIT_UNIX_BACKEND=wayland - pkgs.wayland - - # WINIT_UNIX_BACKEND=x11 - pkgs.xorg.libXcursor - pkgs.xorg.libXrandr - pkgs.xorg.libXi - pkgs.xorg.libX11 - - pkgs.vulkan-headers - pkgs.vulkan-loader - # ------------ fin copie - ]; # https://devenv.sh/languages/ languages.rust.enable = true; - - # AI training with python - enterShell = '' - PYTHONPATH=$PYTHONPATH:$PWD/.devenv/state/venv/lib/python3/site-packages - ''; - - languages.python = { - enable = true; - uv.enable = true; - venv.enable = true; - venv.requirements = " - pip - gymnasium - numpy - stable-baselines3 - shimmy - "; - }; - # https://devenv.sh/scripts/ # scripts.hello.exec = "echo hello from $GREET"; diff --git a/spiel_bot/Cargo.toml b/spiel_bot/Cargo.toml index 1458d66..b541adc 100644 --- a/spiel_bot/Cargo.toml +++ b/spiel_bot/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -trictrac-store = { path = "../store", features = ["python"] } +trictrac-store = { path = "../store" } trictrac-bot = { path = "../bot" } anyhow = "1" rand = "0.9" diff --git a/store/Cargo.toml b/store/Cargo.toml index 58e9e32..d8a594c 100644 --- a/store/Cargo.toml +++ b/store/Cargo.toml @@ -7,26 +7,14 @@ edition = "2021" [lib] name = "trictrac_store" -# "cdylib" → Python .so built by maturin (pyengine) -# "rlib" → used by other workspace crates (bot, client_cli) -# "staticlib" → used by the C++ OpenSpiel game (cxxengine) -crate-type = ["cdylib", "rlib", "staticlib"] - -[features] -# Enable Python bindings (required for maturin / AI training). Not available on wasm32. -python = ["pyo3"] -# Enable C++ bridge for OpenSpiel integration. Not available on wasm32. -cpp = ["dep:cxx"] +crate-type = ["rlib"] [dependencies] anyhow = "1.0" base64 = "0.21.7" -cxx = { version = "1.0", optional = true } # provides macros for creating log messages to be used by a logger (for example env_logger) log = "0.4.20" merge = "0.1.0" -# generate python lib (with maturin) to be used in AI training -pyo3 = { version = "0.23", features = ["extension-module", "abi3-py38"], optional = true } rand = "0.9" serde = { version = "1.0", features = ["derive"] } transpose = "0.2.2" @@ -34,6 +22,3 @@ transpose = "0.2.2" [[bin]] name = "random_game" path = "src/bin/random_game.rs" - -[build-dependencies] -cxx-build = "1.0" diff --git a/store/build.rs b/store/build.rs deleted file mode 100644 index 88d743f..0000000 --- a/store/build.rs +++ /dev/null @@ -1,9 +0,0 @@ -fn main() { - if std::env::var("CARGO_FEATURE_CPP").is_ok() { - cxx_build::bridge("src/cxxengine.rs") - .std("c++17") - .compile("trictrac-cxx"); - - println!("cargo:rerun-if-changed=src/cxxengine.rs"); - } -} diff --git a/store/pyproject.toml b/store/pyproject.toml deleted file mode 100644 index f50d478..0000000 --- a/store/pyproject.toml +++ /dev/null @@ -1,8 +0,0 @@ -[build-system] -requires = ["maturin>=1.0,<2.0"] -build-backend = "maturin" - -[tool.maturin] -# "extension-module" tells pyo3 we want to build an extension module (skips linking against libpython.so) -features = ["pyo3/extension-module"] -# python-source = "python" diff --git a/store/src/cxxengine.rs b/store/src/cxxengine.rs deleted file mode 100644 index 55d348c..0000000 --- a/store/src/cxxengine.rs +++ /dev/null @@ -1,252 +0,0 @@ -//! C++ bindings for the TricTrac game engine via cxx.rs. -//! -//! Exposes an opaque `TricTracEngine` type to C++. The C++ side -//! (open_spiel/games/trictrac/trictrac.cc) holds it via -//! `rust::Box`. -//! -//! The Rust engine always reasons from White's (player 1's) perspective. -//! For Black (player 2), the board is mirrored before computing actions -//! and events are mirrored back before being applied — exactly as in -//! pyengine.rs. - -use std::panic::{self, AssertUnwindSafe}; - -use crate::dice::Dice; -use crate::game::{GameEvent, GameState, Stage, TurnStage}; -use crate::training_common::{get_valid_action_indices, TrictracAction}; - -/// Catch any Rust panic and convert it to anyhow::Error so it never -/// crosses the C FFI boundary as undefined behaviour. -fn catch_panics(f: F) -> anyhow::Result -where - F: FnOnce() -> anyhow::Result + panic::UnwindSafe, -{ - panic::catch_unwind(f).unwrap_or_else(|e| { - let msg = e - .downcast_ref::() - .map(|s| s.as_str()) - .or_else(|| e.downcast_ref::<&str>().copied()) - .unwrap_or("unknown panic payload"); - Err(anyhow::anyhow!("Rust panic in FFI: {}", msg)) - }) -} - -// ── cxx bridge declaration ──────────────────────────────────────────────────── - -#[cxx::bridge(namespace = "trictrac_engine")] -pub mod ffi { - // ── Shared types (transparent to both Rust and C++) ─────────────────────── - - /// Two dice values passed from C++ when applying a chance outcome. - struct DicePair { - die1: u8, - die2: u8, - } - - /// Both players' cumulative scores: holes * 12 + points. - struct PlayerScores { - score_p1: i32, - score_p2: i32, - } - - // ── Opaque Rust type and its free-function constructor ──────────────────── - - extern "Rust" { - /// Opaque handle to a running TricTrac game. - /// C++ accesses this only through `rust::Box`. - type TricTracEngine; - - /// Construct a fresh engine with two players; player 1 (White) goes first. - fn new_trictrac_engine() -> Box; - - /// Deep-copy the engine — required by OpenSpiel's State::Clone(). - fn clone_engine(self: &TricTracEngine) -> Box; - - // ── Queries ─────────────────────────────────────────────────────────── - - /// True when the game is in TurnStage::RollWaiting (OpenSpiel chance node). - fn needs_roll(self: &TricTracEngine) -> bool; - - /// True when Stage::Ended. - fn is_game_ended(self: &TricTracEngine) -> bool; - - /// Active player index: 0 = player 1 (White), 1 = player 2 (Black). - fn current_player_idx(self: &TricTracEngine) -> u64; - - /// Legal action indices for `player_idx` in [0, 513]. - /// Returns an empty vector when it is not that player's turn. - fn get_legal_actions(self: &TricTracEngine, player_idx: u64) -> Result>; - - /// Human-readable description of an action index. - fn action_to_string(self: &TricTracEngine, player_idx: u64, action_idx: u64) -> String; - - /// Both players' scores. - fn get_players_scores(self: &TricTracEngine) -> PlayerScores; - - /// 217-element state tensor (f32), normalized to [0,1]. Mirrored for player_idx == 1. - fn get_tensor(self: &TricTracEngine, player_idx: u64) -> Vec; - - /// Human-readable state description for `player_idx`. - fn get_observation_string(self: &TricTracEngine, player_idx: u64) -> String; - - /// Full debug representation of the current state. - fn to_debug_string(self: &TricTracEngine) -> String; - - // ── Mutations ───────────────────────────────────────────────────────── - - /// Apply a dice-roll result. Returns Err (C++ exception) if not in - /// the RollWaiting stage. - fn apply_dice_roll(self: &mut TricTracEngine, dice: DicePair) -> Result<()>; - - /// Apply a player action. Returns Err (C++ exception) if the action - /// is not legal in the current state. - fn apply_action(self: &mut TricTracEngine, action_idx: u64) -> Result<()>; - } -} - -// ── Opaque type ─────────────────────────────────────────────────────────────── - -pub struct TricTracEngine { - game_state: GameState, -} - -// ── Free-function constructor (declared in the bridge as a plain function) ──── - -pub fn new_trictrac_engine() -> Box { - let mut game_state = GameState::new(false); // schools_enabled = false - game_state.init_player("player1"); - game_state.init_player("player2"); - game_state - .consume(&GameEvent::BeginGame { goes_first: 1 }) - .expect("BeginGame failed during engine initialization"); - Box::new(TricTracEngine { game_state }) -} - -// ── Method implementations ──────────────────────────────────────────────────── - -impl TricTracEngine { - fn clone_engine(&self) -> Box { - Box::new(TricTracEngine { - game_state: self.game_state.clone(), - }) - } - - fn needs_roll(&self) -> bool { - self.game_state.turn_stage == TurnStage::RollWaiting - } - - fn is_game_ended(&self) -> bool { - self.game_state.stage == Stage::Ended - } - - fn current_player_idx(&self) -> u64 { - self.game_state.active_player_id - 1 - } - - fn get_legal_actions(&self, player_idx: u64) -> anyhow::Result> { - if player_idx != self.current_player_idx() { - return Ok(vec![]); - } - catch_panics(AssertUnwindSafe(|| { - if player_idx == 0 { - get_valid_action_indices(&self.game_state) - .map(|v| v.into_iter().map(|i| i as u64).collect()) - } else { - let mirror = self.game_state.mirror(); - get_valid_action_indices(&mirror).map(|v| v.into_iter().map(|i| i as u64).collect()) - } - })) - } - - fn action_to_string(&self, player_idx: u64, action_idx: u64) -> String { - TrictracAction::from_action_index(action_idx as usize) - .map(|a| format!("{}:{}", player_idx, a)) - .unwrap_or_else(|| "unknown action".into()) - } - - fn get_players_scores(&self) -> ffi::PlayerScores { - ffi::PlayerScores { - score_p1: self.score_for(1), - score_p2: self.score_for(2), - } - } - - fn score_for(&self, player_id: u64) -> i32 { - self.game_state - .players - .get(&player_id) - .map(|p| p.holes as i32 * 12 + p.points as i32) - .unwrap_or(-1) - } - - fn get_tensor(&self, player_idx: u64) -> Vec { - if player_idx == 0 { - self.game_state.to_tensor() - } else { - self.game_state.mirror().to_tensor() - } - } - - fn get_observation_string(&self, player_idx: u64) -> String { - if player_idx == 0 { - format!("{}", self.game_state) - } else { - format!("{}", self.game_state.mirror()) - } - } - - fn to_debug_string(&self) -> String { - format!("{}", self.game_state) - } - - fn apply_dice_roll(&mut self, dice: ffi::DicePair) -> anyhow::Result<()> { - if self.game_state.turn_stage != TurnStage::RollWaiting { - anyhow::bail!( - "apply_dice_roll: not in RollWaiting stage (currently {:?})", - self.game_state.turn_stage - ); - } - let player_id = self.game_state.active_player_id; - let dice = Dice { - values: (dice.die1, dice.die2), - }; - catch_panics(AssertUnwindSafe(|| { - self.game_state - .consume(&GameEvent::RollResult { player_id, dice }) - .map_err(|e| anyhow::anyhow!(e)) - })) - } - - fn apply_action(&mut self, action_idx: u64) -> anyhow::Result<()> { - catch_panics(AssertUnwindSafe(|| { - let needs_mirror = self.game_state.active_player_id == 2; - - let event = TrictracAction::from_action_index(action_idx as usize).and_then(|a| { - let state = if needs_mirror { - &self.game_state.mirror() - } else { - &self.game_state - }; - a.to_event(state) - .map(|e| if needs_mirror { e.get_mirror(false) } else { e }) - }); - - match event { - Some(evt) if self.game_state.validate(&evt) => self - .game_state - .consume(&evt) - .map_err(|e| anyhow::anyhow!(e)), - Some(evt) => anyhow::bail!( - "apply_action: event {:?} is not valid in current state {}", - evt, - self.game_state - ), - None => anyhow::bail!( - "apply_action: could not build event from action index {} in state {}", - action_idx, - self.game_state - ), - } - })) - } -} diff --git a/store/src/lib.rs b/store/src/lib.rs index 90fbbc0..5d759b6 100644 --- a/store/src/lib.rs +++ b/store/src/lib.rs @@ -18,11 +18,3 @@ mod dice; pub use dice::{Dice, DiceRoller}; pub mod training_common; - -// python interface "trictrac_engine" (for AI training..) -#[cfg(feature = "python")] -mod pyengine; - -// C++ interface via cxx.rs (for OpenSpiel C++ integration) -#[cfg(feature = "cpp")] -pub mod cxxengine; diff --git a/store/src/player.rs b/store/src/player.rs index cca02b5..d609ce7 100644 --- a/store/src/player.rs +++ b/store/src/player.rs @@ -1,12 +1,9 @@ -#[cfg(feature = "python")] -use pyo3::prelude::*; use serde::{Deserialize, Serialize}; use std::fmt; // This just makes it easier to dissern between a player id and any ol' u64 pub type PlayerId = u64; -#[cfg_attr(feature = "python", pyclass(eq, eq_int))] #[derive(Copy, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Color { White, diff --git a/store/src/pyengine.rs b/store/src/pyengine.rs deleted file mode 100644 index 43b5713..0000000 --- a/store/src/pyengine.rs +++ /dev/null @@ -1,146 +0,0 @@ -//! # Expose trictrac game state and rules in a python module -use pyo3::prelude::*; - -use crate::dice::Dice; -use crate::game::{GameEvent, GameState, Stage, TurnStage}; -use crate::player::PlayerId; -use crate::training_common::{get_valid_action_indices, TrictracAction}; - -#[pyclass] -struct TricTrac { - game_state: GameState, -} - -#[pymethods] -impl TricTrac { - #[new] - fn new() -> Self { - let mut game_state = GameState::new(false); // schools_enabled = false - - // Initialiser 2 joueurs - game_state.init_player("player1"); - game_state.init_player("player2"); - - // Commencer la partie avec le joueur 1 - let _ = game_state.consume(&GameEvent::BeginGame { goes_first: 1 }); - - TricTrac { game_state } - } - - fn needs_roll(&self) -> bool { - self.game_state.turn_stage == TurnStage::RollWaiting - } - - fn is_game_ended(&self) -> bool { - self.game_state.stage == Stage::Ended - } - - // 0 or 1 - fn current_player_idx(&self) -> u64 { - self.game_state.active_player_id - 1 - } - - 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).unwrap() - } else { - let mirror = self.game_state.mirror(); - get_valid_action_indices(&mirror).unwrap() - } - } else { - vec![] - } - } - - fn action_to_string(&self, player_idx: u64, action_idx: usize) -> String { - TrictracAction::from_action_index(action_idx) - .map(|a| format!("{}:{}", player_idx, a)) - .unwrap_or("unknown action".into()) - } - - fn apply_dice_roll(&mut self, dices: (u8, u8)) -> PyResult<()> { - let player_id = self.game_state.active_player_id; - - if self.game_state.turn_stage != TurnStage::RollWaiting { - return Err(pyo3::exceptions::PyRuntimeError::new_err( - "Not in RollWaiting stage", - )); - } - - let dice = Dice { values: dices }; - let _ = self - .game_state - .consume(&GameEvent::RollResult { player_id, dice }); - Ok(()) - } - - fn apply_action(&mut self, action_idx: usize) -> PyResult<()> { - 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) { - let _ = self.game_state.consume(&event); - return Ok(()); - } else { - return Err(pyo3::exceptions::PyRuntimeError::new_err( - "Action is invalid", - )); - } - } - Err(pyo3::exceptions::PyRuntimeError::new_err( - "Could not apply action", - )) - } - - /// Get a player total score (holes & points) - fn get_score(&self, player_id: PlayerId) -> i32 { - if let Some(player) = self.game_state.players.get(&player_id) { - player.holes as i32 * 12 + player.points as i32 - } else { - -1 - } - } - - fn get_players_scores(&self) -> [i32; 2] { - [self.get_score(1), self.get_score(2)] - } - - fn get_tensor(&self, player_idx: u64) -> Vec { - if player_idx == 0 { - self.game_state.to_tensor() - } else { - self.game_state.mirror().to_tensor() - } - } - - 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) - fn __str__(&self) -> String { - format!("{}", self.game_state) - } -} - -/// A Python module implemented in Rust. The name of this function must match -/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to -/// import the module. -#[pymodule] -fn trictrac_store(m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_class::()?; - - Ok(()) -}