Compare commits
No commits in common. "develop" and "main" have entirely different histories.
2362
Cargo.lock
generated
2362
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -20,7 +20,7 @@ serde_json = "1.0"
|
||||||
store = { path = "../store" }
|
store = { path = "../store" }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
env_logger = "0.10"
|
env_logger = "0.10"
|
||||||
burn = { version = "0.18", features = ["ndarray", "autodiff"] }
|
burn = { version = "0.17", features = ["ndarray", "autodiff"] }
|
||||||
burn-rl = { git = "https://github.com/yunjhongwu/burn-rl-examples.git", package = "burn-rl" }
|
burn-rl = { git = "https://github.com/yunjhongwu/burn-rl-examples.git", package = "burn-rl" }
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
confy = "1.0.0"
|
confy = "1.0.0"
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,6 @@ 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) + mouvements possibles
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
/// Types d'actions possibles dans le jeu
|
/// Types d'actions possibles dans le jeu
|
||||||
#[derive(Debug, Copy, Clone, Eq, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, Serialize, Deserialize, PartialEq)]
|
||||||
pub enum TrictracAction {
|
pub enum TrictracAction {
|
||||||
|
|
@ -162,7 +158,10 @@ impl TrictracAction {
|
||||||
|
|
||||||
/// Retourne la taille de l'espace d'actions total
|
/// Retourne la taille de l'espace d'actions total
|
||||||
pub fn action_space_size() -> usize {
|
pub fn action_space_size() -> usize {
|
||||||
ACTION_SPACE_SIZE
|
// 1 (Roll) + 1 (Go) + mouvements possibles
|
||||||
|
// Pour les mouvements : 2*25*25 = 1250 (choix du dé + position 0-24 pour chaque from)
|
||||||
|
// Mais on peut optimiser en limitant aux positions valides (1-24)
|
||||||
|
2 + (2 * 16 * 16) // = 514
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn to_game_event(&self, player_id: PlayerId, dice: Dice) -> GameEvent {
|
// pub fn to_game_event(&self, player_id: PlayerId, dice: Dice) -> GameEvent {
|
||||||
|
|
@ -226,11 +225,7 @@ pub fn get_valid_actions(game_state: &crate::GameState) -> Vec<TrictracAction> {
|
||||||
}
|
}
|
||||||
TurnStage::Move => {
|
TurnStage::Move => {
|
||||||
let rules = store::MoveRules::new(&color, &game_state.board, game_state.dice);
|
let rules = store::MoveRules::new(&color, &game_state.board, game_state.dice);
|
||||||
let mut possible_moves = rules.get_possible_moves_sequences(true, vec![]);
|
let possible_moves = rules.get_possible_moves_sequences(true, vec![]);
|
||||||
if possible_moves.is_empty() {
|
|
||||||
// Empty move
|
|
||||||
possible_moves.push((CheckerMove::default(), CheckerMove::default()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modififier checker_moves_to_trictrac_action si on doit gérer Black
|
// Modififier checker_moves_to_trictrac_action si on doit gérer Black
|
||||||
assert_eq!(color, store::Color::White);
|
assert_eq!(color, store::Color::White);
|
||||||
|
|
|
||||||
|
|
@ -146,11 +146,7 @@ pub fn get_valid_actions(game_state: &crate::GameState) -> Vec<TrictracAction> {
|
||||||
}
|
}
|
||||||
TurnStage::Move => {
|
TurnStage::Move => {
|
||||||
let rules = store::MoveRules::new(&color, &game_state.board, game_state.dice);
|
let rules = store::MoveRules::new(&color, &game_state.board, game_state.dice);
|
||||||
let mut possible_moves = rules.get_possible_moves_sequences(true, vec![]);
|
let possible_moves = rules.get_possible_moves_sequences(true, vec![]);
|
||||||
if possible_moves.is_empty() {
|
|
||||||
// Empty move
|
|
||||||
possible_moves.push((CheckerMove::default(), CheckerMove::default()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modififier checker_moves_to_trictrac_action si on doit gérer Black
|
// Modififier checker_moves_to_trictrac_action si on doit gérer Black
|
||||||
assert_eq!(color, store::Color::White);
|
assert_eq!(color, store::Color::White);
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,10 @@ use board_game::board::{
|
||||||
use board_game::impl_unit_symmetry_board;
|
use board_game::impl_unit_symmetry_board;
|
||||||
use internal_iterator::InternalIterator;
|
use internal_iterator::InternalIterator;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::hash::Hash;
|
|
||||||
use std::ops::ControlFlow;
|
use std::ops::ControlFlow;
|
||||||
use store::Color;
|
use store::Color;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct TrictracBoard(crate::GameState);
|
pub struct TrictracBoard(crate::GameState);
|
||||||
|
|
||||||
impl Default for TrictracBoard {
|
impl Default for TrictracBoard {
|
||||||
|
|
@ -78,20 +77,6 @@ impl BoardGameBoard for TrictracBoard {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TrictracBoard {
|
|
||||||
pub fn inner(&self) -> &crate::GameState {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_fen(&self) -> String {
|
|
||||||
self.0.to_string_id()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_fen(fen: &str) -> Result<TrictracBoard, String> {
|
|
||||||
crate::GameState::from_string_id(fen).map(TrictracBoard)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> BoardMoves<'a, TrictracBoard> for TrictracBoard {
|
impl<'a> BoardMoves<'a, TrictracBoard> for TrictracBoard {
|
||||||
type AllMovesIterator = TrictracAllMovesIterator;
|
type AllMovesIterator = TrictracAllMovesIterator;
|
||||||
type AvailableMovesIterator = TrictracAvailableMovesIterator<'a>;
|
type AvailableMovesIterator = TrictracAvailableMovesIterator<'a>;
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ nombres aléatoires avec seed : <https://richard.dallaway.com/posts/2021-01-04-r
|
||||||
- rayon ( sync <-> parallel )
|
- rayon ( sync <-> parallel )
|
||||||
|
|
||||||
- front : yew + tauri
|
- front : yew + tauri
|
||||||
|
|
||||||
- egui
|
- egui
|
||||||
|
|
||||||
- <https://docs.rs/board-game/latest/board_game/>
|
- <https://docs.rs/board-game/latest/board_game/>
|
||||||
|
|
@ -32,7 +31,6 @@ nombres aléatoires avec seed : <https://richard.dallaway.com/posts/2021-01-04-r
|
||||||
- <https://www.mattkeeter.com/projects/pont/>
|
- <https://www.mattkeeter.com/projects/pont/>
|
||||||
- <https://github.com/jackadamson/onitama> (wasm, rooms)
|
- <https://github.com/jackadamson/onitama> (wasm, rooms)
|
||||||
- <https://github.com/UkoeHB/renet2>
|
- <https://github.com/UkoeHB/renet2>
|
||||||
- <https://github.com/UkoeHB/bevy_simplenet>
|
|
||||||
|
|
||||||
## Others
|
## Others
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -639,55 +639,6 @@ impl Board {
|
||||||
self.positions[field - 1] += unit;
|
self.positions[field - 1] += unit;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_gnupg_pos_id(bits: &str) -> Result<Board, String> {
|
|
||||||
let mut positions = [0i8; 24];
|
|
||||||
let mut bit_idx = 0;
|
|
||||||
let bit_chars: Vec<char> = bits.chars().collect();
|
|
||||||
|
|
||||||
// White checkers (points 1 to 24)
|
|
||||||
for i in 0..24 {
|
|
||||||
if bit_idx >= bit_chars.len() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let mut count = 0;
|
|
||||||
while bit_idx < bit_chars.len() && bit_chars[bit_idx] == '1' {
|
|
||||||
count += 1;
|
|
||||||
bit_idx += 1;
|
|
||||||
}
|
|
||||||
positions[i] = count;
|
|
||||||
if bit_idx < bit_chars.len() && bit_chars[bit_idx] == '0' {
|
|
||||||
bit_idx += 1; // Consume the '0' separator
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Black checkers (points 24 down to 1)
|
|
||||||
for i in (0..24).rev() {
|
|
||||||
if bit_idx >= bit_chars.len() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let mut count = 0;
|
|
||||||
while bit_idx < bit_chars.len() && bit_chars[bit_idx] == '1' {
|
|
||||||
count += 1;
|
|
||||||
bit_idx += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if positions[i] == 0 {
|
|
||||||
positions[i] = -count;
|
|
||||||
} else if count > 0 {
|
|
||||||
return Err(format!(
|
|
||||||
"Invalid board: checkers of both colors on point {}",
|
|
||||||
i + 1
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if bit_idx < bit_chars.len() && bit_chars[bit_idx] == '0' {
|
|
||||||
bit_idx += 1; // Consume the '0' separator
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Board { positions })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unit Tests
|
// Unit Tests
|
||||||
|
|
|
||||||
|
|
@ -55,17 +55,6 @@ impl Dice {
|
||||||
format!("{:0>3b}{:0>3b}", self.values.0, self.values.1)
|
format!("{:0>3b}{:0>3b}", self.values.0, self.values.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_bits_string(bits: &str) -> Result<Self, String> {
|
|
||||||
if bits.len() != 6 {
|
|
||||||
return Err("Invalid bit string length for dice".to_string());
|
|
||||||
}
|
|
||||||
let d1_str = &bits[0..3];
|
|
||||||
let d2_str = &bits[3..6];
|
|
||||||
let d1 = u8::from_str_radix(d1_str, 2).map_err(|e| e.to_string())?;
|
|
||||||
let d2 = u8::from_str_radix(d2_str, 2).map_err(|e| e.to_string())?;
|
|
||||||
Ok(Dice { values: (d1, d2) })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_display_string(self) -> String {
|
pub fn to_display_string(self) -> String {
|
||||||
format!("{} & {}", self.values.0, self.values.1)
|
format!("{} & {}", self.values.0, self.values.1)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,12 @@ use log::{debug, error};
|
||||||
// use itertools::Itertools;
|
// use itertools::Itertools;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
use std::{fmt, str};
|
use std::{fmt, str};
|
||||||
|
|
||||||
use base64::{engine::general_purpose, Engine as _};
|
use base64::{engine::general_purpose, Engine as _};
|
||||||
|
|
||||||
/// The different stages a game can be in. (not to be confused with the entire "GameState")
|
/// The different stages a game can be in. (not to be confused with the entire "GameState")
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub enum Stage {
|
pub enum Stage {
|
||||||
PreGame,
|
PreGame,
|
||||||
InGame,
|
InGame,
|
||||||
|
|
@ -23,7 +22,7 @@ pub enum Stage {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The different stages a game turn can be in.
|
/// The different stages a game turn can be in.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub enum TurnStage {
|
pub enum TurnStage {
|
||||||
RollDice,
|
RollDice,
|
||||||
RollWaiting,
|
RollWaiting,
|
||||||
|
|
@ -115,11 +114,6 @@ impl Default for GameState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Hash for GameState {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
self.to_string_id().hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GameState {
|
impl GameState {
|
||||||
/// Create a new default game
|
/// Create a new default game
|
||||||
|
|
@ -259,7 +253,7 @@ impl GameState {
|
||||||
pos_bits.push_str(&white_bits);
|
pos_bits.push_str(&white_bits);
|
||||||
pos_bits.push_str(&black_bits);
|
pos_bits.push_str(&black_bits);
|
||||||
|
|
||||||
pos_bits = format!("{pos_bits:0<108}");
|
pos_bits = format!("{pos_bits:0>108}");
|
||||||
// println!("{}", pos_bits);
|
// println!("{}", pos_bits);
|
||||||
let pos_u8 = pos_bits
|
let pos_u8 = pos_bits
|
||||||
.as_bytes()
|
.as_bytes()
|
||||||
|
|
@ -270,81 +264,6 @@ impl GameState {
|
||||||
general_purpose::STANDARD.encode(pos_u8)
|
general_purpose::STANDARD.encode(pos_u8)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_string_id(id: &str) -> Result<Self, String> {
|
|
||||||
let bytes = general_purpose::STANDARD
|
|
||||||
.decode(id)
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let bits_str: String = bytes.iter().map(|byte| format!("{:06b}", byte)).collect();
|
|
||||||
|
|
||||||
// The original string was padded to 108 bits.
|
|
||||||
let bits = if bits_str.len() >= 108 {
|
|
||||||
&bits_str[..108]
|
|
||||||
} else {
|
|
||||||
return Err("Invalid decoded string length".to_string());
|
|
||||||
};
|
|
||||||
|
|
||||||
let board_bits = &bits[0..77];
|
|
||||||
let board = Board::from_gnupg_pos_id(board_bits)?;
|
|
||||||
|
|
||||||
let active_player_bit = bits.chars().nth(77).unwrap();
|
|
||||||
let active_player_color = if active_player_bit == '1' {
|
|
||||||
Color::Black
|
|
||||||
} else {
|
|
||||||
Color::White
|
|
||||||
};
|
|
||||||
|
|
||||||
let turn_stage_bits = &bits[78..81];
|
|
||||||
let turn_stage = match turn_stage_bits {
|
|
||||||
"000" => TurnStage::RollWaiting,
|
|
||||||
"001" => TurnStage::RollDice,
|
|
||||||
"010" => TurnStage::MarkPoints,
|
|
||||||
"011" => TurnStage::HoldOrGoChoice,
|
|
||||||
"100" => TurnStage::Move,
|
|
||||||
"101" => TurnStage::MarkAdvPoints,
|
|
||||||
_ => return Err(format!("Invalid bits for turn stage : {turn_stage_bits}")),
|
|
||||||
};
|
|
||||||
|
|
||||||
let dice_bits = &bits[81..87];
|
|
||||||
let dice = Dice::from_bits_string(dice_bits).map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let white_player_bits = &bits[87..97];
|
|
||||||
let black_player_bits = &bits[97..107];
|
|
||||||
|
|
||||||
let white_player =
|
|
||||||
Player::from_bits_string(white_player_bits, "Player 1".to_string(), Color::White)
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
let black_player =
|
|
||||||
Player::from_bits_string(black_player_bits, "Player 2".to_string(), Color::Black)
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let mut players = HashMap::new();
|
|
||||||
players.insert(1, white_player);
|
|
||||||
players.insert(2, black_player);
|
|
||||||
|
|
||||||
let active_player_id = if active_player_color == Color::White {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
2
|
|
||||||
};
|
|
||||||
|
|
||||||
// Some fields are not in the ID, so we use defaults.
|
|
||||||
Ok(GameState {
|
|
||||||
stage: Stage::InGame, // Assume InGame from ID
|
|
||||||
turn_stage,
|
|
||||||
board,
|
|
||||||
active_player_id,
|
|
||||||
players,
|
|
||||||
history: Vec::new(),
|
|
||||||
dice,
|
|
||||||
dice_points: (0, 0),
|
|
||||||
dice_moves: (CheckerMove::default(), CheckerMove::default()),
|
|
||||||
dice_jans: PossibleJans::default(),
|
|
||||||
roll_first: false, // Assume not first roll
|
|
||||||
schools_enabled: false, // Assume disabled
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn who_plays(&self) -> Option<&Player> {
|
pub fn who_plays(&self) -> Option<&Player> {
|
||||||
self.get_active_player()
|
self.get_active_player()
|
||||||
}
|
}
|
||||||
|
|
@ -931,16 +850,7 @@ mod tests {
|
||||||
let state = init_test_gamestate(TurnStage::RollDice);
|
let state = init_test_gamestate(TurnStage::RollDice);
|
||||||
let string_id = state.to_string_id();
|
let string_id = state.to_string_id();
|
||||||
// println!("string_id : {}", string_id);
|
// println!("string_id : {}", string_id);
|
||||||
assert_eq!(string_id, "Pz84AAAABz8/AAAAAAgAASAG");
|
assert_eq!(string_id, "Hz88AAAAAz8/IAAAAAQAADAD");
|
||||||
let new_state = GameState::from_string_id(&string_id).unwrap();
|
|
||||||
assert_eq!(state.board, new_state.board);
|
|
||||||
assert_eq!(state.active_player_id, new_state.active_player_id);
|
|
||||||
assert_eq!(state.turn_stage, new_state.turn_stage);
|
|
||||||
assert_eq!(state.dice, new_state.dice);
|
|
||||||
assert_eq!(
|
|
||||||
state.get_white_player().unwrap().points,
|
|
||||||
new_state.get_white_player().unwrap().points
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -53,26 +53,6 @@ impl Player {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_bits_string(bits: &str, name: String, color: Color) -> Result<Self, String> {
|
|
||||||
if bits.len() != 10 {
|
|
||||||
return Err("Invalid bit string length for player".to_string());
|
|
||||||
}
|
|
||||||
let points = u8::from_str_radix(&bits[0..4], 2).map_err(|e| e.to_string())?;
|
|
||||||
let holes = u8::from_str_radix(&bits[4..8], 2).map_err(|e| e.to_string())?;
|
|
||||||
let can_bredouille = bits.chars().nth(8).unwrap() == '1';
|
|
||||||
let can_big_bredouille = bits.chars().nth(9).unwrap() == '1';
|
|
||||||
|
|
||||||
Ok(Player {
|
|
||||||
name,
|
|
||||||
color,
|
|
||||||
points,
|
|
||||||
holes,
|
|
||||||
can_bredouille,
|
|
||||||
can_big_bredouille,
|
|
||||||
dice_roll_count: 0, // This info is not in the string id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_vec(&self) -> Vec<u8> {
|
pub fn to_vec(&self) -> Vec<u8> {
|
||||||
vec![
|
vec![
|
||||||
self.points,
|
self.points,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue