From f2a89f60bc79a620da386c6ad7601eb1ca9ca259 Mon Sep 17 00:00:00 2001 From: Henri Bourcereau Date: Tue, 26 Aug 2025 21:04:13 +0200 Subject: [PATCH] feat: Karel Peeters board game implementation --- Cargo.lock | 251 ++++++++++++++++++++++++++++++++-- bot/Cargo.toml | 2 + bot/src/burnrl/environment.rs | 73 +--------- bot/src/lib.rs | 1 + bot/src/training_common.rs | 85 +++++++++++- bot/src/trictrac_board.rs | 149 ++++++++++++++++++++ store/src/board.rs | 4 +- store/src/dice.rs | 2 +- store/src/game.rs | 15 +- store/src/player.rs | 4 +- 10 files changed, 494 insertions(+), 92 deletions(-) create mode 100644 bot/src/trictrac_board.rs diff --git a/Cargo.lock b/Cargo.lock index 3708d45..d0f6183 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + [[package]] name = "adler2" version = "2.0.1" @@ -158,6 +167,24 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "arimaa_engine_step" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1c6726d7896a539a62e157b05fa4b7308ffb7872f2b4a2a592d5adb19837861" +dependencies = [ + "anyhow", + "itertools 0.10.5", + "log", + "regex", +] + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "arrayvec" version = "0.7.6" @@ -204,7 +231,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f3efb2ca85bc610acfa917b5aaa36f3fcbebed5b3182d7f877b02531c4b80c8" dependencies = [ "anyhow", - "arrayvec", + "arrayvec 0.7.6", "log", "nom", "num-rational", @@ -217,7 +244,22 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98922d6a4cfbcb08820c69d8eeccc05bb1f29bfa06b4f5b1dbfe9a868bd7608e" dependencies = [ - "arrayvec", + "arrayvec 0.7.6", +] + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -314,14 +356,39 @@ dependencies = [ "generic-array", ] +[[package]] +name = "board-game" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "647fc8459363368aae04df3d21da37094430c57dd993d09be2792133d5365e3e" +dependencies = [ + "arimaa_engine_step", + "cast_trait", + "chess", + "decorum", + "internal-iterator", + "itertools 0.10.5", + "lazy_static", + "nohash-hasher", + "nom", + "num-traits", + "once_cell", + "rand 0.8.5", + "rand_xoshiro", + "rayon", + "static_assertions", +] + [[package]] name = "bot" version = "0.1.0" dependencies = [ + "board-game", "burn", "burn-rl", "confy", "env_logger 0.10.0", + "internal-iterator", "log", "pretty_assertions", "rand 0.8.5", @@ -797,6 +864,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" +[[package]] +name = "cast_trait" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f8d981c476baadf74cd52897866a1d279d3e14e2d5e2d9af045210e0ae6128" + [[package]] name = "castaway" version = "0.2.3" @@ -863,6 +936,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "chess" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ed299b171ec34f372945ad6726f7bc1d2afd5f59fb8380f64f48e2bab2f0ec8" +dependencies = [ + "arrayvec 0.5.2", + "failure", + "nodrop", + "rand 0.7.3", +] + [[package]] name = "cipher" version = "0.4.4" @@ -1446,6 +1531,15 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +[[package]] +name = "decorum" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "281759d3c8a14f5c3f0c49363be56810fcd7f910422f97f2db850c2920fde5cf" +dependencies = [ + "num-traits", +] + [[package]] name = "deranged" version = "0.4.0" @@ -1759,6 +1853,28 @@ dependencies = [ "zune-inflate", ] +[[package]] +name = "failure" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure 0.12.6", +] + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -2192,6 +2308,12 @@ dependencies = [ "weezl", ] +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + [[package]] name = "gix-features" version = "0.42.1" @@ -2374,7 +2496,7 @@ dependencies = [ "num-traits", "ordered-float 5.0.0", "rand 0.8.5", - "rand_pcg", + "rand_pcg 0.3.1", "sdl2", "serde", ] @@ -2573,6 +2695,12 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "internal-iterator" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "969ee3fc68ec2e88eb21434ce4d9b7e1600d1ce92ff974560a6c4a304f5124b9" + [[package]] name = "interpolate_name" version = "0.2.4" @@ -2601,6 +2729,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.12.1" @@ -2959,7 +3096,7 @@ version = "25.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632" dependencies = [ - "arrayvec", + "arrayvec 0.7.6", "bit-set", "bitflags 2.9.1", "cfg_aliases", @@ -3036,6 +3173,18 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nom" version = "7.1.3" @@ -3235,6 +3384,15 @@ dependencies = [ "malloc_buf", ] +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + [[package]] name = "octets" version = "0.2.0" @@ -3592,6 +3750,18 @@ dependencies = [ "uuid", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg 0.2.1", +] + [[package]] name = "rand" version = "0.8.5" @@ -3614,6 +3784,16 @@ dependencies = [ "rand_core 0.9.3", ] +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -3634,6 +3814,12 @@ dependencies = [ "rand_core 0.9.3", ] +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + [[package]] name = "rand_core" version = "0.6.4" @@ -3663,6 +3849,24 @@ dependencies = [ "rand 0.9.1", ] +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + [[package]] name = "rand_pcg" version = "0.3.1" @@ -3672,6 +3876,15 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "range-alloc" version = "0.1.4" @@ -3729,7 +3942,7 @@ checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" dependencies = [ "arbitrary", "arg_enum_proc_macro", - "arrayvec", + "arrayvec 0.7.6", "av1-grain", "bitstream-io", "built", @@ -4013,6 +4226,12 @@ dependencies = [ "smallvec", ] +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -4522,6 +4741,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -5273,7 +5504,7 @@ version = "25.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec8fb398f119472be4d80bc3647339f56eb63b2a331f6a3d16e25d8144197dd9" dependencies = [ - "arrayvec", + "arrayvec 0.7.6", "bitflags 2.9.1", "cfg_aliases", "document-features", @@ -5301,7 +5532,7 @@ version = "25.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7b882196f8368511d613c6aeec80655160db6646aebddf8328879a88d54e500" dependencies = [ - "arrayvec", + "arrayvec 0.7.6", "bit-set", "bit-vec", "bitflags 2.9.1", @@ -5360,7 +5591,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f968767fe4d3d33747bbd1473ccd55bf0f6451f55d733b5597e67b5deab4ad17" dependencies = [ "android_system_properties", - "arrayvec", + "arrayvec 0.7.6", "ash", "bit-set", "bitflags 2.9.1", @@ -5783,7 +6014,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.104", - "synstructure", + "synstructure 0.13.2", ] [[package]] @@ -5824,7 +6055,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.104", - "synstructure", + "synstructure 0.13.2", ] [[package]] diff --git a/bot/Cargo.toml b/bot/Cargo.toml index fe918bd..21e0128 100644 --- a/bot/Cargo.toml +++ b/bot/Cargo.toml @@ -24,3 +24,5 @@ burn = { version = "0.17", features = ["ndarray", "autodiff"] } burn-rl = { git = "https://github.com/yunjhongwu/burn-rl-examples.git", package = "burn-rl" } log = "0.4.20" confy = "1.0.0" +board-game = "0.8.2" +internal-iterator = "0.2.3" diff --git a/bot/src/burnrl/environment.rs b/bot/src/burnrl/environment.rs index c74cf64..50daf11 100644 --- a/bot/src/burnrl/environment.rs +++ b/bot/src/burnrl/environment.rs @@ -281,79 +281,8 @@ impl TrictracEnvironment { let mut reward = 0.0; let mut is_rollpoint = false; - let event = match action { - TrictracAction::Roll => { - // Lancer les dés - Some(GameEvent::Roll { - player_id: self.active_player_id, - }) - } - // TrictracAction::Mark => { - // // Marquer des points - // let points = self.game. - // Some(GameEvent::Mark { - // player_id: self.active_player_id, - // points, - // }) - // } - TrictracAction::Go => { - // Continuer après avoir gagné un trou - Some(GameEvent::Go { - player_id: self.active_player_id, - }) - } - TrictracAction::Move { - dice_order, - checker1, - checker2, - } => { - // Effectuer un mouvement - let (dice1, dice2) = if dice_order { - (self.game.dice.values.0, self.game.dice.values.1) - } else { - (self.game.dice.values.1, self.game.dice.values.0) - }; - - let color = &store::Color::White; - let from1 = self - .game - .board - .get_checker_field(color, checker1 as u8) - .unwrap_or(0); - let mut to1 = from1 + dice1 as usize; - let checker_move1 = store::CheckerMove::new(from1, to1).unwrap_or_default(); - - let mut tmp_board = self.game.board.clone(); - let move_result = tmp_board.move_checker(color, checker_move1); - if move_result.is_err() { - None - // panic!("Error while moving checker {move_result:?}") - } else { - let from2 = tmp_board - .get_checker_field(color, checker2 as u8) - .unwrap_or(0); - let mut to2 = from2 + dice2 as usize; - - // Gestion prise de coin par puissance - let opp_rest_field = 13; - if to1 == opp_rest_field && to2 == opp_rest_field { - to1 -= 1; - to2 -= 1; - } - - let checker_move1 = store::CheckerMove::new(from1, to1).unwrap_or_default(); - let checker_move2 = store::CheckerMove::new(from2, to2).unwrap_or_default(); - - Some(GameEvent::Move { - player_id: self.active_player_id, - moves: (checker_move1, checker_move2), - }) - } - } - }; - // Appliquer l'événement si valide - if let Some(event) = event { + if let Some(event) = action.to_event(&self.game) { if self.game.validate(&event) { self.game.consume(&event); reward += REWARD_VALID_MOVE; diff --git a/bot/src/lib.rs b/bot/src/lib.rs index 6e3b269..dab36be 100644 --- a/bot/src/lib.rs +++ b/bot/src/lib.rs @@ -3,6 +3,7 @@ pub mod dqn_simple; pub mod strategy; pub mod training_common; pub mod training_common_big; +pub mod trictrac_board; use log::debug; use store::{CheckerMove, Color, GameEvent, GameState, PlayerId, PointsRules, Stage, TurnStage}; diff --git a/bot/src/training_common.rs b/bot/src/training_common.rs index b2f2bad..0a581dd 100644 --- a/bot/src/training_common.rs +++ b/bot/src/training_common.rs @@ -1,10 +1,11 @@ use std::cmp::{max, min}; +use std::fmt::{Debug, Display, Formatter}; use serde::{Deserialize, Serialize}; -use store::CheckerMove; +use store::{CheckerMove, GameEvent, GameState}; /// Types d'actions possibles dans le jeu -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, Serialize, Deserialize, PartialEq)] pub enum TrictracAction { /// Lancer les dés Roll, @@ -20,6 +21,14 @@ pub enum TrictracAction { // Mark, } +impl Display for TrictracAction { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let s = format!("{self:?}"); + writeln!(f, "{}", s.chars().rev().collect::())?; + Ok(()) + } +} + impl TrictracAction { /// Encode une action en index pour le réseau de neurones pub fn to_action_index(&self) -> usize { @@ -44,6 +53,78 @@ impl TrictracAction { } } + pub fn to_event(&self, state: &GameState) -> Option { + match self { + TrictracAction::Roll => { + // Lancer les dés + Some(GameEvent::Roll { + player_id: state.active_player_id, + }) + } + // TrictracAction::Mark => { + // // Marquer des points + // let points = self.game. + // Some(GameEvent::Mark { + // player_id: self.active_player_id, + // points, + // }) + // } + TrictracAction::Go => { + // Continuer après avoir gagné un trou + Some(GameEvent::Go { + player_id: state.active_player_id, + }) + } + TrictracAction::Move { + dice_order, + checker1, + checker2, + } => { + // Effectuer un mouvement + let (dice1, dice2) = if *dice_order { + (state.dice.values.0, state.dice.values.1) + } else { + (state.dice.values.1, state.dice.values.0) + }; + + let color = &store::Color::White; + let from1 = state + .board + .get_checker_field(color, *checker1 as u8) + .unwrap_or(0); + let mut to1 = from1 + dice1 as usize; + let checker_move1 = store::CheckerMove::new(from1, to1).unwrap_or_default(); + + let mut tmp_board = state.board.clone(); + let move_result = tmp_board.move_checker(color, checker_move1); + if move_result.is_err() { + None + // panic!("Error while moving checker {move_result:?}") + } else { + let from2 = tmp_board + .get_checker_field(color, *checker2 as u8) + .unwrap_or(0); + let mut to2 = from2 + dice2 as usize; + + // Gestion prise de coin par puissance + let opp_rest_field = 13; + if to1 == opp_rest_field && to2 == opp_rest_field { + to1 -= 1; + to2 -= 1; + } + + let checker_move1 = store::CheckerMove::new(from1, to1).unwrap_or_default(); + let checker_move2 = store::CheckerMove::new(from2, to2).unwrap_or_default(); + + Some(GameEvent::Move { + player_id: state.active_player_id, + moves: (checker_move1, checker_move2), + }) + } + } + } + } + /// Décode un index d'action en TrictracAction pub fn from_action_index(index: usize) -> Option { match index { diff --git a/bot/src/trictrac_board.rs b/bot/src/trictrac_board.rs new file mode 100644 index 0000000..01b2a82 --- /dev/null +++ b/bot/src/trictrac_board.rs @@ -0,0 +1,149 @@ +// https://docs.rs/board-game/ implementation +use crate::training_common::{get_valid_actions, TrictracAction}; +use board_game::board::{ + Board as BoardGameBoard, BoardDone, BoardMoves, Outcome, PlayError, Player as BoardGamePlayer, +}; +use board_game::impl_unit_symmetry_board; +use internal_iterator::InternalIterator; +use std::fmt; +use std::ops::ControlFlow; +use store::Color; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TrictracBoard(crate::GameState); + +impl Default for TrictracBoard { + fn default() -> Self { + TrictracBoard(crate::GameState::new_with_players("white", "black")) + } +} + +impl fmt::Display for TrictracBoard { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl_unit_symmetry_board!(TrictracBoard); + +impl BoardGameBoard for TrictracBoard { + // impl TrictracBoard { + type Move = TrictracAction; + + fn next_player(&self) -> BoardGamePlayer { + self.0 + .who_plays() + .map(|p| { + if p.color == Color::Black { + BoardGamePlayer::B + } else { + BoardGamePlayer::A + } + }) + .unwrap_or(BoardGamePlayer::A) + } + + fn is_available_move(&self, mv: Self::Move) -> Result { + self.check_done()?; + let is_valid = mv + .to_event(&self.0) + .map(|evt| self.0.validate(&evt)) + .unwrap_or(false); + Ok(is_valid) + } + + fn play(&mut self, mv: Self::Move) -> Result<(), PlayError> { + self.check_can_play(mv)?; + self.0.consume(&mv.to_event(&self.0).unwrap()); + Ok(()) + } + + fn outcome(&self) -> Option { + if self.0.stage == crate::Stage::Ended { + self.0.determine_winner().map(|player_id| { + Outcome::WonBy(if player_id == 1 { + BoardGamePlayer::A + } else { + BoardGamePlayer::B + }) + }) + } else { + None + } + } + + fn can_lose_after_move() -> bool { + true + } +} + +impl<'a> BoardMoves<'a, TrictracBoard> for TrictracBoard { + type AllMovesIterator = TrictracAllMovesIterator; + type AvailableMovesIterator = TrictracAvailableMovesIterator<'a>; + + fn all_possible_moves() -> Self::AllMovesIterator { + TrictracAllMovesIterator::default() + } + + fn available_moves(&'a self) -> Result { + TrictracAvailableMovesIterator::new(self) + } +} + +#[derive(Debug, Clone)] +pub struct TrictracAllMovesIterator; + +impl Default for TrictracAllMovesIterator { + fn default() -> Self { + TrictracAllMovesIterator + } +} + +impl InternalIterator for TrictracAllMovesIterator { + type Item = TrictracAction; + + fn try_for_each ControlFlow>(self, mut f: F) -> ControlFlow { + f(TrictracAction::Roll)?; + f(TrictracAction::Go)?; + for dice_order in [false, true] { + for checker1 in 0..16 { + for checker2 in 0..16 { + f(TrictracAction::Move { + dice_order, + checker1, + checker2, + })?; + } + } + } + + ControlFlow::Continue(()) + } +} + +#[derive(Debug, Clone)] +pub struct TrictracAvailableMovesIterator<'a> { + board: &'a TrictracBoard, +} + +impl<'a> TrictracAvailableMovesIterator<'a> { + pub fn new(board: &'a TrictracBoard) -> Result { + board.check_done()?; + Ok(TrictracAvailableMovesIterator { board }) + } + + pub fn board(&self) -> &'a TrictracBoard { + self.board + } +} + +impl InternalIterator for TrictracAvailableMovesIterator<'_> { + type Item = TrictracAction; + + fn try_for_each(self, f: F) -> ControlFlow + where + F: FnMut(Self::Item) -> ControlFlow, + { + get_valid_actions(&self.board.0).into_iter().try_for_each(f) + } +} diff --git a/store/src/board.rs b/store/src/board.rs index da0bae8..d0f3615 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, Deserialize)] +#[derive(Debug, Copy, Clone, Serialize, PartialEq, Eq, Deserialize)] pub struct CheckerMove { from: Field, to: Field, @@ -94,7 +94,7 @@ impl CheckerMove { } /// Represents the Tric Trac board -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Board { positions: [i8; 24], } diff --git a/store/src/dice.rs b/store/src/dice.rs index 3f3f9f6..348410d 100644 --- a/store/src/dice.rs +++ b/store/src/dice.rs @@ -44,7 +44,7 @@ impl DiceRoller { /// Represents the two dice /// /// Trictrac is always played with two dice. -#[derive(Debug, Clone, Copy, Serialize, PartialEq, Deserialize, Default)] +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, Deserialize, Default)] pub struct Dice { /// The two dice values pub values: (u8, u8), diff --git a/store/src/game.rs b/store/src/game.rs index f8a1276..b63ffcd 100644 --- a/store/src/game.rs +++ b/store/src/game.rs @@ -60,7 +60,7 @@ impl From for u8 { } /// Represents a TricTrac game -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct GameState { pub stage: Stage, pub turn_stage: TurnStage, @@ -123,6 +123,15 @@ impl GameState { gs } + pub fn new_with_players(p1_name: &str, p2_name: &str) -> Self { + 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 }); + } + game + } + fn set_schools_enabled(&mut self, schools_enabled: bool) { self.schools_enabled = schools_enabled; } @@ -707,14 +716,14 @@ impl GameState { } /// The reasons why a game could end -#[derive(Debug, Clone, Copy, Serialize, PartialEq, Deserialize)] +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, Deserialize)] pub enum EndGameReason { PlayerLeft { player_id: PlayerId }, PlayerWon { winner: PlayerId }, } /// An event that progresses the GameState forward -#[derive(Debug, Clone, Serialize, PartialEq, Deserialize)] +#[derive(Debug, Clone, Serialize, PartialEq, Eq, Deserialize)] pub enum GameEvent { BeginGame { goes_first: PlayerId, diff --git a/store/src/player.rs b/store/src/player.rs index cf31953..d42120b 100644 --- a/store/src/player.rs +++ b/store/src/player.rs @@ -4,7 +4,7 @@ use std::fmt; // This just makes it easier to dissern between a player id and any ol' u64 pub type PlayerId = u64; -#[derive(Copy, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Copy, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Color { White, Black, @@ -20,7 +20,7 @@ impl Color { } /// Struct for storing player related data. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Player { pub name: String, pub color: Color,