From 24ddcce23382d79573659b0fdcdfe1e922cf7976 Mon Sep 17 00:00:00 2001 From: Henri Bourcereau Date: Sun, 24 Mar 2024 18:37:35 +0100 Subject: [PATCH] bot --- Cargo.lock | 9 ++++ Cargo.toml | 1 + bot/Cargo.toml | 10 ++++ bot/src/bot.rs | 0 bot/src/lib.rs | 108 ++++++++++++++++++++++++++++++++++++++++++ client_cli/Cargo.toml | 1 + client_cli/src/app.rs | 31 ++++++++---- store/src/board.rs | 28 +++++++++++ store/src/game.rs | 8 ++++ store/src/lib.rs | 2 +- 10 files changed, 188 insertions(+), 10 deletions(-) create mode 100644 bot/Cargo.toml create mode 100644 bot/src/bot.rs create mode 100644 bot/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 7ff8537..37c03fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1109,6 +1109,14 @@ dependencies = [ "objc2-encode", ] +[[package]] +name = "bot" +version = "0.1.0" +dependencies = [ + "pretty_assertions", + "store", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -1242,6 +1250,7 @@ version = "0.1.0" dependencies = [ "anyhow", "bincode", + "bot", "pico-args", "pretty_assertions", "renet", diff --git a/Cargo.toml b/Cargo.toml index e521f37..23931f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "client", "client_tui", "client_cli", + "bot", "server", "store" ] diff --git a/bot/Cargo.toml b/bot/Cargo.toml new file mode 100644 index 0000000..ca8f005 --- /dev/null +++ b/bot/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "bot" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +pretty_assertions = "1.4.0" +store = { path = "../store" } diff --git a/bot/src/bot.rs b/bot/src/bot.rs new file mode 100644 index 0000000..e69de29 diff --git a/bot/src/lib.rs b/bot/src/lib.rs new file mode 100644 index 0000000..e8b7108 --- /dev/null +++ b/bot/src/lib.rs @@ -0,0 +1,108 @@ +mod bot; + +use store::{CheckerMove, Color, Dice, GameEvent, GameState, Player, PlayerId, Stage, TurnStage}; + +#[derive(Debug)] +pub struct Bot { + pub game: GameState, + player_id: PlayerId, + color: Color, +} + +impl Default for Bot { + fn default() -> Bot { + Bot { + game: GameState::default(), + player_id: 1, + color: Color::Black, + } + } +} + +// impl PlayerEngine for Bot {} + +impl Bot { + /// new initialize a bot + /// # Examples + /// ```let mut bot = Bot::new(Color::Black); + /// assert_eq!(bot.game.stage, Stage::PreGame); + /// ``` + pub fn new(color: Color) -> Self { + let mut game = GameState::default(); + game.init_player("p1"); + game.init_player("p2"); + + let player_id = match color { + Color::White => 1, + Color::Black => 2, + }; + + Self { + game, + player_id, + color, + } + } + + pub fn consume(&mut self, event: &GameEvent) -> Option { + self.game.consume(event); + // println!("{:?}", self.game); + if self.game.active_player_id == self.player_id { + return match self.game.turn_stage { + TurnStage::RollDice => Some(GameEvent::Roll { + player_id: self.player_id, + }), + TurnStage::MarkPoints => Some(GameEvent::Mark { + player_id: self.player_id, + points: 0, + }), + TurnStage::Move => Some(GameEvent::Move { + player_id: self.player_id, + moves: self.choose_move(), + }), + }; + } + None + } + + fn choose_move(&self) -> (CheckerMove, CheckerMove) { + let (dice1, dice2) = match self.color { + Color::White => self.game.dice.values, + Color::Black => (0 - self.game.dice.values.0, 0 - self.game.dice.values.1), + }; + + let fields = self.game.board.get_color_fields(self.color); + let first_field = fields.first().unwrap(); + ( + CheckerMove::new(first_field.0, first_field.0 + dice1 as usize).unwrap(), + CheckerMove::new(first_field.0, first_field.0 + dice2 as usize).unwrap(), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new() { + let bot = Bot::new(Color::Black); + assert_eq!(bot.game.stage, Stage::PreGame); + } + + #[test] + fn test_consume() { + let mut bot = Bot::new(Color::Black); + let mut event = bot.consume(&GameEvent::BeginGame { goes_first: 2 }); + assert_eq!(event, Some(GameEvent::Roll { player_id: 2 })); + + event = bot.consume(&GameEvent::BeginGame { goes_first: 1 }); + assert_eq!(event, None); + + event = bot.consume(&GameEvent::RollResult { + player_id: 2, + dice: Dice { values: (2, 3) }, + }); + assert_eq!(bot.game.turn_stage, TurnStage::MarkPoints); + } +} diff --git a/client_cli/Cargo.toml b/client_cli/Cargo.toml index 1b08baa..37c7323 100644 --- a/client_cli/Cargo.toml +++ b/client_cli/Cargo.toml @@ -12,3 +12,4 @@ pico-args = "0.5.0" pretty_assertions = "1.4.0" renet = "0.0.13" store = { path = "../store" } +bot = { path = "../bot" } diff --git a/client_cli/src/app.rs b/client_cli/src/app.rs index 7f2658a..7e9a52d 100644 --- a/client_cli/src/app.rs +++ b/client_cli/src/app.rs @@ -1,5 +1,6 @@ +use bot::Bot; use pretty_assertions::assert_eq; -use store::{CheckerMove, Dice, DiceRoller, GameEvent, GameState, PlayerId}; +use store::{CheckerMove, Color, Dice, DiceRoller, GameEvent, GameState, PlayerId}; #[derive(Debug, Default)] pub struct AppArgs { @@ -13,6 +14,7 @@ pub struct Game { pub dice_roller: DiceRoller, first_move: Option, player_id: Option, + bot: Bot, } impl Game { @@ -21,16 +23,27 @@ impl Game { let mut state = GameState::default(); // local : player let player_id: Option = state.init_player("myself"); - state.init_player("adversary"); - state.consume(&GameEvent::BeginGame { - goes_first: player_id.unwrap(), - }); - Self { + // bot + let bot_id: PlayerId = state.init_player("bot").unwrap(); + let bot_color = state.player_color_by_id(&bot_id).unwrap(); + let bot: Bot = Bot::new(bot_color); + + let mut game = Self { state, dice_roller: DiceRoller::new(seed), first_move: None, player_id, - } + bot, + }; + game.consume(&GameEvent::BeginGame { + goes_first: player_id.unwrap(), + }); + game + } + + pub fn consume(&mut self, event: &GameEvent) -> Option { + self.state.consume(&event); + self.bot.consume(&event) } } @@ -78,7 +91,7 @@ impl App { return; } let dice = self.game.dice_roller.roll(); - self.game.state.consume(&GameEvent::RollResult { + self.game.consume(&GameEvent::RollResult { player_id: self.game.player_id.unwrap(), dice, }); @@ -106,7 +119,7 @@ impl App { self.game.first_move = None; return; } - self.game.state.consume(&move_event); + self.game.consume(&move_event); self.game.first_move = None; } else { self.game.first_move = Some(checker_move.unwrap()); diff --git a/store/src/board.rs b/store/src/board.rs index aa959f9..de956aa 100644 --- a/store/src/board.rs +++ b/store/src/board.rs @@ -296,6 +296,27 @@ impl Board { self.get_field_checkers(field).map(|(count, color)| color) } + /// returns the list of Fields containing Checkers of the Color + pub fn get_color_fields(&self, color: Color) -> Vec<(usize, i8)> { + match color { + Color::White => self + .positions + .iter() + .enumerate() + .filter(|&(_, count)| *count > 0) + .map(|(i, count)| (i + 1, *count)) + .collect(), + Color::Black => self + .positions + .iter() + .enumerate() + .filter(|&(_, count)| *count < 0) + .rev() + .map(|(i, count)| (i + 1, (0 - count))) + .collect(), + } + } + // Get the corner field for the color pub fn get_color_corner(&self, color: &Color) -> Field { if color == &Color::White { @@ -407,4 +428,11 @@ mod tests { let player = Player::new("".into(), Color::White); assert!(board.set(&Color::White, 23, -3).is_err()); } + + #[test] + fn get_color_fields() { + let board = Board::new(); + assert_eq!(board.get_color_fields(Color::White), vec![(1, 15)]); + assert_eq!(board.get_color_fields(Color::Black), vec![(24, 15)]); + } } diff --git a/store/src/game.rs b/store/src/game.rs index 0475677..32b2aef 100644 --- a/store/src/game.rs +++ b/store/src/game.rs @@ -165,6 +165,14 @@ impl GameState { .next() } + pub fn player_color_by_id(&self, player_id: &PlayerId) -> Option { + self.players + .iter() + .filter(|(id, _)| *id == player_id) + .map(|(_, player)| player.color) + .next() + } + // ---------------------------------------------------------------------------------- // Rules checks // ---------------------------------------------------------------------------------- diff --git a/store/src/lib.rs b/store/src/lib.rs index 9eed587..cbdcf64 100644 --- a/store/src/lib.rs +++ b/store/src/lib.rs @@ -1,5 +1,5 @@ mod game; -pub use game::{EndGameReason, GameEvent, GameState, Stage}; +pub use game::{EndGameReason, GameEvent, GameState, Stage, TurnStage}; mod player; pub use player::{Color, Player, PlayerId};