Compare commits
No commits in common. "fe3bfe47db4191abdf0e9e1ddde263f0202bff80" and "7c50a6d07b10e7dfd4052d22cfad585cc824b908" have entirely different histories.
fe3bfe47db
...
7c50a6d07b
14 changed files with 16 additions and 514 deletions
79
Cargo.lock
generated
79
Cargo.lock
generated
|
|
@ -3460,15 +3460,6 @@ dependencies = [
|
||||||
"stable_deref_trait",
|
"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]]
|
[[package]]
|
||||||
name = "merge"
|
name = "merge"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -4219,69 +4210,6 @@ dependencies = [
|
||||||
"num-traits",
|
"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.106",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[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.106",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "qoi"
|
name = "qoi"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
|
@ -5226,7 +5154,6 @@ dependencies = [
|
||||||
"base64 0.21.7",
|
"base64 0.21.7",
|
||||||
"log",
|
"log",
|
||||||
"merge",
|
"merge",
|
||||||
"pyo3",
|
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"serde",
|
"serde",
|
||||||
"transpose",
|
"transpose",
|
||||||
|
|
@ -5965,12 +5892,6 @@ version = "0.2.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unindent"
|
|
||||||
version = "0.2.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "universal-hash"
|
name = "universal-hash"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
|
|
||||||
|
|
@ -1,9 +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"
|
|
||||||
# module-name = "trictrac.game"
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
import store
|
|
||||||
|
|
||||||
game = store.TricTrac()
|
|
||||||
print(game.get_state_dict())
|
|
||||||
|
|
@ -237,7 +237,7 @@ impl TrictracEnvironment {
|
||||||
|
|
||||||
// Mapper l'index d'action sur une action valide
|
// Mapper l'index d'action sur une action valide
|
||||||
let action_index = (action.index as usize) % valid_actions.len();
|
let action_index = (action.index as usize) % valid_actions.len();
|
||||||
Some(valid_actions[action_index])
|
Some(valid_actions[action_index].clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Exécute une action Trictrac dans le jeu
|
/// Exécute une action Trictrac dans le jeu
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ use std::fmt::{Debug, Display, Formatter};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use store::{CheckerMove, GameEvent, GameState};
|
use store::{CheckerMove, GameEvent, GameState};
|
||||||
|
|
||||||
// 1 (Roll) + 1 (Go) + 512 (mouvements possibles)
|
// 1 (Roll) + 1 (Go) + mouvements possibles
|
||||||
// avec 512 = 2 (choix du dé) * 16 * 16 (choix de la dame 0-15 pour chaque from)
|
// Pour les mouvements : 2*16*16 = 514 (choix du dé + choix de la dame 0-15 pour chaque from)
|
||||||
pub const ACTION_SPACE_SIZE: usize = 514;
|
pub const ACTION_SPACE_SIZE: usize = 514;
|
||||||
|
|
||||||
/// Types d'actions possibles dans le jeu
|
/// Types d'actions possibles dans le jeu
|
||||||
|
|
|
||||||
24
devenv.lock
24
devenv.lock
|
|
@ -3,10 +3,10 @@
|
||||||
"devenv": {
|
"devenv": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"dir": "src/modules",
|
"dir": "src/modules",
|
||||||
"lastModified": 1768056019,
|
"lastModified": 1753667201,
|
||||||
"owner": "cachix",
|
"owner": "cachix",
|
||||||
"repo": "devenv",
|
"repo": "devenv",
|
||||||
"rev": "9bfc4a64c3a798ed8fa6cee3a519a9eac5e73cb5",
|
"rev": "4d584d7686a50387f975879788043e55af9f0ad4",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -19,14 +19,14 @@
|
||||||
"flake-compat": {
|
"flake-compat": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1767039857,
|
"lastModified": 1747046372,
|
||||||
"owner": "NixOS",
|
"owner": "edolstra",
|
||||||
"repo": "flake-compat",
|
"repo": "flake-compat",
|
||||||
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "edolstra",
|
||||||
"repo": "flake-compat",
|
"repo": "flake-compat",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
|
|
@ -40,10 +40,10 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1767281941,
|
"lastModified": 1750779888,
|
||||||
"owner": "cachix",
|
"owner": "cachix",
|
||||||
"repo": "git-hooks.nix",
|
"repo": "git-hooks.nix",
|
||||||
"rev": "f0927703b7b1c8d97511c4116eb9b4ec6645a0fa",
|
"rev": "16ec914f6fb6f599ce988427d9d94efddf25fe6d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -60,10 +60,10 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1762808025,
|
"lastModified": 1709087332,
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "gitignore.nix",
|
"repo": "gitignore.nix",
|
||||||
"rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c",
|
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -74,10 +74,10 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1767995494,
|
"lastModified": 1753432016,
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "45a1530683263666f42d1de4cdda328109d5a676",
|
"rev": "6027c30c8e9810896b92429f0092f624f7b1aace",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
||||||
25
devenv.nix
25
devenv.nix
|
|
@ -15,12 +15,6 @@
|
||||||
pkgs.samply # code profiler
|
pkgs.samply # code profiler
|
||||||
pkgs.feedgnuplot # to visualize bots training results
|
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
|
# for bevy
|
||||||
pkgs.alsa-lib
|
pkgs.alsa-lib
|
||||||
pkgs.udev
|
pkgs.udev
|
||||||
|
|
@ -53,25 +47,6 @@
|
||||||
# https://devenv.sh/languages/
|
# https://devenv.sh/languages/
|
||||||
languages.rust.enable = true;
|
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/
|
# https://devenv.sh/scripts/
|
||||||
# scripts.hello.exec = "echo hello from $GREET";
|
# scripts.hello.exec = "echo hello from $GREET";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,10 +53,6 @@ Client
|
||||||
|
|
||||||
### Epic : Bot
|
### Epic : Bot
|
||||||
|
|
||||||
- PGX
|
|
||||||
- https://joe-antognini.github.io/ml/jax-tic-tac-toe
|
|
||||||
- https://www.sotets.uk/pgx/api_usage/
|
|
||||||
|
|
||||||
- OpenAi gym
|
- OpenAi gym
|
||||||
- doc gymnasium <https://gymnasium.farama.org/introduction/basic_usage/>
|
- doc gymnasium <https://gymnasium.farama.org/introduction/basic_usage/>
|
||||||
- Rust implementation for OpenAi gym <https://github.com/MathisWellmann/gym-rs>
|
- Rust implementation for OpenAi gym <https://github.com/MathisWellmann/gym-rs>
|
||||||
|
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
# Python bindings
|
|
||||||
|
|
||||||
## Génération bindings
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Generate trictrac python lib as a wheel
|
|
||||||
maturin build -m store/Cargo.toml --release
|
|
||||||
# Install wheel in local python env
|
|
||||||
pip install --no-deps --force-reinstall --prefix .devenv/state/venv target/wheels/*.whl
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Pour vérifier l'accès à la lib : lancer le shell interactif `python`
|
|
||||||
|
|
||||||
```python
|
|
||||||
Python 3.13.11 (main, Dec 5 2025, 16:06:33) [GCC 15.2.0] on linux
|
|
||||||
Type "help", "copyright", "credits" or "license" for more information.
|
|
||||||
>>> import store
|
|
||||||
>>> game = store.TricTrac()
|
|
||||||
>>> game.get_active_player_id()
|
|
||||||
1
|
|
||||||
```
|
|
||||||
|
|
||||||
### Appels depuis python
|
|
||||||
|
|
||||||
`python bot/python/test.py`
|
|
||||||
|
|
||||||
## Interfaces
|
|
||||||
|
|
||||||
## Entraînement
|
|
||||||
1
justfile
1
justfile
|
|
@ -20,7 +20,6 @@ profile:
|
||||||
cargo build --profile profiling
|
cargo build --profile profiling
|
||||||
samply record ./target/profiling/client_cli --bot dummy,dummy
|
samply record ./target/profiling/client_cli --bot dummy,dummy
|
||||||
pythonlib:
|
pythonlib:
|
||||||
rm -rf target/wheels
|
|
||||||
maturin build -m store/Cargo.toml --release
|
maturin build -m store/Cargo.toml --release
|
||||||
pip install --no-deps --force-reinstall --prefix .devenv/state/venv target/wheels/*.whl
|
pip install --no-deps --force-reinstall --prefix .devenv/state/venv target/wheels/*.whl
|
||||||
trainbot algo:
|
trainbot algo:
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,14 @@ edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "store"
|
name = "store"
|
||||||
# "cdylib" is necessary to produce a shared library for Python to import from.
|
|
||||||
# Only "rlib" is needed for other Rust crates to use this library
|
# Only "rlib" is needed for other Rust crates to use this library
|
||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["rlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.21.7"
|
base64 = "0.21.7"
|
||||||
# provides macros for creating log messages to be used by a logger (for example env_logger)
|
# provides macros for creating log messages to be used by a logger (for example env_logger)
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
merge = "0.1.0"
|
merge = "0.1.0"
|
||||||
# generate python lib (with maturin) to be used in AI training
|
|
||||||
pyo3 = { version = "0.23", features = ["extension-module", "abi3-py38"] }
|
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
transpose = "0.2.2"
|
transpose = "0.2.2"
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,3 @@ pub use board::CheckerMove;
|
||||||
|
|
||||||
mod dice;
|
mod dice;
|
||||||
pub use dice::{Dice, DiceRoller};
|
pub use dice::{Dice, DiceRoller};
|
||||||
|
|
||||||
// python interface "trictrac_engine" (for AI training..)
|
|
||||||
mod pyengine;
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
use pyo3::prelude::*;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
// This just makes it easier to dissern between a player id and any ol' u64
|
// This just makes it easier to dissern between a player id and any ol' u64
|
||||||
pub type PlayerId = u64;
|
pub type PlayerId = u64;
|
||||||
|
|
||||||
#[pyclass(eq, eq_int)]
|
|
||||||
#[derive(Copy, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Copy, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum Color {
|
pub enum Color {
|
||||||
White,
|
White,
|
||||||
|
|
|
||||||
|
|
@ -1,337 +0,0 @@
|
||||||
//! # Expose trictrac game state and rules in a python module
|
|
||||||
use pyo3::prelude::*;
|
|
||||||
use pyo3::types::PyDict;
|
|
||||||
|
|
||||||
use crate::board::CheckerMove;
|
|
||||||
use crate::dice::Dice;
|
|
||||||
use crate::game::{GameEvent, GameState, Stage, TurnStage};
|
|
||||||
use crate::game_rules_moves::MoveRules;
|
|
||||||
use crate::game_rules_points::PointsRules;
|
|
||||||
use crate::player::{Color, PlayerId};
|
|
||||||
|
|
||||||
#[pyclass]
|
|
||||||
struct TricTrac {
|
|
||||||
game_state: GameState,
|
|
||||||
dice_roll_sequence: Vec<(u8, u8)>,
|
|
||||||
current_dice_index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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("bot");
|
|
||||||
|
|
||||||
// Commencer la partie avec le joueur 1
|
|
||||||
game_state.consume(&GameEvent::BeginGame { goes_first: 1 });
|
|
||||||
|
|
||||||
TricTrac {
|
|
||||||
game_state,
|
|
||||||
dice_roll_sequence: Vec::new(),
|
|
||||||
current_dice_index: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Obtenir l'état du jeu sous forme de chaîne de caractères compacte
|
|
||||||
fn get_state_id(&self) -> String {
|
|
||||||
self.game_state.to_string_id()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Obtenir l'état du jeu sous forme de dictionnaire pour faciliter l'entrainement
|
|
||||||
fn get_state_dict(&self) -> PyResult<Py<PyDict>> {
|
|
||||||
Python::with_gil(|py| {
|
|
||||||
let state_dict = PyDict::new(py);
|
|
||||||
|
|
||||||
// Informations essentielles sur l'état du jeu
|
|
||||||
state_dict.set_item("active_player", self.game_state.active_player_id)?;
|
|
||||||
state_dict.set_item("stage", format!("{:?}", self.game_state.stage))?;
|
|
||||||
state_dict.set_item("turn_stage", format!("{:?}", self.game_state.turn_stage))?;
|
|
||||||
|
|
||||||
// Dés
|
|
||||||
let (dice1, dice2) = self.game_state.dice.values;
|
|
||||||
state_dict.set_item("dice", (dice1, dice2))?;
|
|
||||||
|
|
||||||
// Points des joueurs
|
|
||||||
if let Some(white_player) = self.game_state.get_white_player() {
|
|
||||||
state_dict.set_item("white_points", white_player.points)?;
|
|
||||||
state_dict.set_item("white_holes", white_player.holes)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(black_player) = self.game_state.get_black_player() {
|
|
||||||
state_dict.set_item("black_points", black_player.points)?;
|
|
||||||
state_dict.set_item("black_holes", black_player.holes)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Positions des pièces
|
|
||||||
let white_positions = self.get_checker_positions(Color::White);
|
|
||||||
let black_positions = self.get_checker_positions(Color::Black);
|
|
||||||
|
|
||||||
state_dict.set_item("white_positions", white_positions)?;
|
|
||||||
state_dict.set_item("black_positions", black_positions)?;
|
|
||||||
|
|
||||||
// État compact pour la comparaison d'états
|
|
||||||
state_dict.set_item("state_id", self.game_state.to_string_id())?;
|
|
||||||
|
|
||||||
Ok(state_dict.into())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Renvoie les positions des pièces pour un joueur spécifique
|
|
||||||
fn get_checker_positions(&self, color: Color) -> Vec<(usize, i8)> {
|
|
||||||
self.game_state.board.get_color_fields(color)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Obtenir la liste des mouvements légaux sous forme de paires (from, to)
|
|
||||||
fn get_available_moves(&self) -> Vec<((usize, usize), (usize, usize))> {
|
|
||||||
// L'agent joue toujours le joueur actif
|
|
||||||
let color = self
|
|
||||||
.game_state
|
|
||||||
.player_color_by_id(&self.game_state.active_player_id)
|
|
||||||
.unwrap_or(Color::White);
|
|
||||||
|
|
||||||
// Si ce n'est pas le moment de déplacer les pièces, retourner une liste vide
|
|
||||||
if self.game_state.turn_stage != TurnStage::Move
|
|
||||||
&& self.game_state.turn_stage != TurnStage::HoldOrGoChoice
|
|
||||||
{
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
|
|
||||||
let rules = MoveRules::new(&color, &self.game_state.board, self.game_state.dice);
|
|
||||||
let possible_moves = rules.get_possible_moves_sequences(true, vec![]);
|
|
||||||
|
|
||||||
// Convertir les mouvements CheckerMove en tuples (from, to) pour Python
|
|
||||||
possible_moves
|
|
||||||
.into_iter()
|
|
||||||
.map(|(move1, move2)| {
|
|
||||||
(
|
|
||||||
(move1.get_from(), move1.get_to()),
|
|
||||||
(move2.get_from(), move2.get_to()),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Jouer un coup ((from1, to1), (from2, to2))
|
|
||||||
fn play_move(&mut self, moves: ((usize, usize), (usize, usize))) -> bool {
|
|
||||||
let ((from1, to1), (from2, to2)) = moves;
|
|
||||||
|
|
||||||
// Vérifier que c'est au tour du joueur de jouer
|
|
||||||
if self.game_state.turn_stage != TurnStage::Move
|
|
||||||
&& self.game_state.turn_stage != TurnStage::HoldOrGoChoice
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let move1 = CheckerMove::new(from1, to1).unwrap_or_default();
|
|
||||||
let move2 = CheckerMove::new(from2, to2).unwrap_or_default();
|
|
||||||
|
|
||||||
let event = GameEvent::Move {
|
|
||||||
player_id: self.game_state.active_player_id,
|
|
||||||
moves: (move1, move2),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Vérifier si le mouvement est valide
|
|
||||||
if !self.game_state.validate(&event) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exécuter le mouvement
|
|
||||||
self.game_state.consume(&event);
|
|
||||||
|
|
||||||
// Si l'autre joueur doit lancer les dés maintenant, simuler ce lancement
|
|
||||||
if self.game_state.turn_stage == TurnStage::RollDice {
|
|
||||||
self.roll_dice();
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Lancer les dés (soit aléatoirement, soit en utilisant une séquence prédéfinie)
|
|
||||||
fn roll_dice(&mut self) -> (u8, u8) {
|
|
||||||
// Vérifier que c'est au bon moment pour lancer les dés
|
|
||||||
if self.game_state.turn_stage != TurnStage::RollDice
|
|
||||||
&& self.game_state.turn_stage != TurnStage::RollWaiting
|
|
||||||
{
|
|
||||||
return self.game_state.dice.values;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simuler un lancer de dés
|
|
||||||
let dice_values = if !self.dice_roll_sequence.is_empty()
|
|
||||||
&& self.current_dice_index < self.dice_roll_sequence.len()
|
|
||||||
{
|
|
||||||
// Utiliser la séquence prédéfinie
|
|
||||||
let dice = self.dice_roll_sequence[self.current_dice_index];
|
|
||||||
self.current_dice_index += 1;
|
|
||||||
dice
|
|
||||||
} else {
|
|
||||||
// Générer aléatoirement
|
|
||||||
(
|
|
||||||
(1 + (rand::random::<u8>() % 6)),
|
|
||||||
(1 + (rand::random::<u8>() % 6)),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Envoyer les événements appropriés
|
|
||||||
let roll_event = GameEvent::Roll {
|
|
||||||
player_id: self.game_state.active_player_id,
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.game_state.validate(&roll_event) {
|
|
||||||
self.game_state.consume(&roll_event);
|
|
||||||
}
|
|
||||||
|
|
||||||
let roll_result_event = GameEvent::RollResult {
|
|
||||||
player_id: self.game_state.active_player_id,
|
|
||||||
dice: Dice {
|
|
||||||
values: dice_values,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.game_state.validate(&roll_result_event) {
|
|
||||||
self.game_state.consume(&roll_result_event);
|
|
||||||
}
|
|
||||||
|
|
||||||
dice_values
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Marquer des points
|
|
||||||
fn mark_points(&mut self, points: u8) -> bool {
|
|
||||||
// Vérifier que c'est au bon moment pour marquer des points
|
|
||||||
if self.game_state.turn_stage != TurnStage::MarkPoints
|
|
||||||
&& self.game_state.turn_stage != TurnStage::MarkAdvPoints
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let event = GameEvent::Mark {
|
|
||||||
player_id: self.game_state.active_player_id,
|
|
||||||
points,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Vérifier si l'événement est valide
|
|
||||||
if !self.game_state.validate(&event) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exécuter l'événement
|
|
||||||
self.game_state.consume(&event);
|
|
||||||
|
|
||||||
// Si l'autre joueur doit lancer les dés maintenant, simuler ce lancement
|
|
||||||
if self.game_state.turn_stage == TurnStage::RollDice {
|
|
||||||
self.roll_dice();
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Choisir de "continuer" (Go) après avoir gagné un trou
|
|
||||||
fn choose_go(&mut self) -> bool {
|
|
||||||
// Vérifier que c'est au bon moment pour choisir de continuer
|
|
||||||
if self.game_state.turn_stage != TurnStage::HoldOrGoChoice {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let event = GameEvent::Go {
|
|
||||||
player_id: self.game_state.active_player_id,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Vérifier si l'événement est valide
|
|
||||||
if !self.game_state.validate(&event) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exécuter l'événement
|
|
||||||
self.game_state.consume(&event);
|
|
||||||
|
|
||||||
// Simuler le lancer de dés pour le prochain tour
|
|
||||||
self.roll_dice();
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calcule les points maximaux que le joueur actif peut obtenir avec les dés actuels
|
|
||||||
fn calculate_points(&self) -> u8 {
|
|
||||||
let active_player = self
|
|
||||||
.game_state
|
|
||||||
.players
|
|
||||||
.get(&self.game_state.active_player_id);
|
|
||||||
|
|
||||||
if let Some(player) = active_player {
|
|
||||||
let dice_roll_count = player.dice_roll_count;
|
|
||||||
let color = player.color;
|
|
||||||
|
|
||||||
let points_rules =
|
|
||||||
PointsRules::new(&color, &self.game_state.board, self.game_state.dice);
|
|
||||||
let (points, _) = points_rules.get_points(dice_roll_count);
|
|
||||||
|
|
||||||
points
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Réinitialise la partie
|
|
||||||
fn reset(&mut self) {
|
|
||||||
self.game_state = GameState::new(false);
|
|
||||||
|
|
||||||
// Initialiser 2 joueurs
|
|
||||||
self.game_state.init_player("player1");
|
|
||||||
self.game_state.init_player("bot");
|
|
||||||
|
|
||||||
// Commencer la partie avec le joueur 1
|
|
||||||
self.game_state
|
|
||||||
.consume(&GameEvent::BeginGame { goes_first: 1 });
|
|
||||||
|
|
||||||
// Réinitialiser l'index de la séquence de dés
|
|
||||||
self.current_dice_index = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Vérifie si la partie est terminée
|
|
||||||
fn is_done(&self) -> bool {
|
|
||||||
self.game_state.stage == Stage::Ended || self.game_state.determine_winner().is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Obtenir le gagnant de la partie
|
|
||||||
fn get_winner(&self) -> Option<PlayerId> {
|
|
||||||
self.game_state.determine_winner()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Obtenir le score du joueur actif (nombre de trous)
|
|
||||||
fn get_score(&self, player_id: PlayerId) -> i32 {
|
|
||||||
if let Some(player) = self.game_state.players.get(&player_id) {
|
|
||||||
player.holes as i32
|
|
||||||
} else {
|
|
||||||
-1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Obtenir l'ID du joueur actif
|
|
||||||
fn get_active_player_id(&self) -> PlayerId {
|
|
||||||
self.game_state.active_player_id
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Définir une séquence de dés à utiliser (pour la reproductibilité)
|
|
||||||
fn set_dice_sequence(&mut self, sequence: Vec<(u8, u8)>) {
|
|
||||||
self.dice_roll_sequence = sequence;
|
|
||||||
self.current_dice_index = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 store(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
||||||
m.add_class::<TricTrac>()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue