diff --git a/Cargo.lock b/Cargo.lock index cf0ce3f..8edeb6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -632,6 +632,7 @@ dependencies = [ "rand", "rocket", "serde", + "serde_json", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d448270..6f9ebe7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,4 @@ rocket = { version = "0.5.0", features = ["json"] } serde = "1.0.196" rand = "0.8.5" +serde_json = "1.0.113" diff --git a/src/azul.rs b/src/azul.rs index b417844..a91d8c1 100644 --- a/src/azul.rs +++ b/src/azul.rs @@ -6,11 +6,8 @@ use serde::{Deserialize, Serialize}; use rand::distributions::WeightedIndex; use rand::prelude::*; -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize)] struct GameState { - #[serde(skip)] - #[serde(default = "make_rng")] - rng: StdRng, n_players: usize, current_player: usize, starting_player: usize, @@ -22,10 +19,32 @@ struct GameState { factories: Vec, market: TileSetWithStart, players: Vec, + #[serde(skip)] + #[serde(default = "make_rng")] + rng: Box, } -fn make_rng() -> StdRng { - StdRng::from_entropy() +fn make_rng() -> Box { + Box::new(StdRng::from_entropy()) +} + +impl Clone for GameState { + fn clone(&self) -> Self { + GameState { + n_players: self.n_players, + current_player: self.current_player, + starting_player: self.starting_player, + game_end: self.game_end, + rounds: self.rounds, + days: self.days, + bag: self.bag.clone(), + lid: self.lid.clone(), + factories: self.factories.clone(), + market: self.market.clone(), + players: self.players.clone(), + rng: make_rng(), + } + } } impl GameState { @@ -56,7 +75,6 @@ impl GameState { } let game = GameState { - rng: StdRng::from_entropy(), n_players, current_player: 0, starting_player: 0, @@ -81,6 +99,7 @@ impl GameState { white: 0, }, players, + rng: Box::new(StdRng::from_entropy()), }; Ok(game) @@ -90,7 +109,7 @@ impl GameState { /// Will replenish the bag from the lid when empty /// Will return with partially filled factories if out of tiles fn fill(&mut self) -> Result<(), &'static str> { - if !self.is_empty() { + if !self.only_start() { return Err("Cannot fill, there are still tiles left to be picked"); } @@ -101,28 +120,29 @@ impl GameState { self.lid = TileSet::default(); } else if self.bag.is_empty() { return Ok(()); - } else { - let choices = [ - Color::Blue, - Color::Yellow, - Color::Red, - Color::Black, - Color::White, - ]; - let weights = [ - self.bag.blue, - self.bag.yellow, - self.bag.red, - self.bag.black, - self.bag.white, - ]; - - let dist = WeightedIndex::new(&weights).unwrap(); - let picked = choices[dist.sample(&mut self.rng)]; - - self.bag.add_color(picked, 1)?; - factory.add_color(picked, 1)?; } + let choices = [ + Color::Blue, + Color::Yellow, + Color::Red, + Color::Black, + Color::White, + ]; + let weights = [ + self.bag.blue, + self.bag.yellow, + self.bag.red, + self.bag.black, + self.bag.white, + ]; + + let dist = WeightedIndex::new(&weights).unwrap(); + let picked = choices[dist.sample(&mut self.rng)]; + + eprintln!("picked {:?}", picked); + + self.bag.add_color(picked, -1)?; + factory.add_color(picked, 1)?; } } @@ -137,6 +157,16 @@ impl GameState { factories && market } + /// Returns whether or not only the start tile is in play + fn only_start(&self) -> bool { + let factories = self.factories.iter().all(|f| f.is_empty()); + let market = Color::Blue + .into_iter() + .all(|c| self.market.get_color(c) == 0); + + factories && market + } + /// Scores the game and clears the boards into the lid /// Doesn't check if game is ready to be scored! fn score_unchecked(&mut self) -> Result<(), &'static str> { @@ -378,16 +408,16 @@ impl TileSet { self.blue = usize::checked_add_signed(self.blue, n).ok_or("would overflow!")? } Color::Yellow => { - self.blue = usize::checked_add_signed(self.yellow, n).ok_or("would overflow")? + self.yellow = usize::checked_add_signed(self.yellow, n).ok_or("would overflow")? } Color::Red => { - self.blue = usize::checked_add_signed(self.red, n).ok_or("would overflow")? + self.red = usize::checked_add_signed(self.red, n).ok_or("would overflow")? } Color::Black => { - self.blue = usize::checked_add_signed(self.black, n).ok_or("would overflow")? + self.black = usize::checked_add_signed(self.black, n).ok_or("would overflow")? } Color::White => { - self.blue = usize::checked_add_signed(self.white, n).ok_or("would overflow")? + self.white = usize::checked_add_signed(self.white, n).ok_or("would overflow")? } Color::Start => return Err("tried to add Start tiles to TileSet"), } @@ -483,16 +513,16 @@ impl TileSetWithStart { self.blue = usize::checked_add_signed(self.blue, n).ok_or("would overflow!")? } Color::Yellow => { - self.blue = usize::checked_add_signed(self.yellow, n).ok_or("would overflow")? + self.yellow = usize::checked_add_signed(self.yellow, n).ok_or("would overflow")? } Color::Red => { - self.blue = usize::checked_add_signed(self.red, n).ok_or("would overflow")? + self.red = usize::checked_add_signed(self.red, n).ok_or("would overflow")? } Color::Black => { - self.blue = usize::checked_add_signed(self.black, n).ok_or("would overflow")? + self.black = usize::checked_add_signed(self.black, n).ok_or("would overflow")? } Color::White => { - self.blue = usize::checked_add_signed(self.white, n).ok_or("would overflow")? + self.white = usize::checked_add_signed(self.white, n).ok_or("would overflow")? } Color::Start if n == 1 && self.start == 0 => { self.start = usize::checked_add_signed(self.white, n).ok_or("would overflow")? @@ -800,3 +830,21 @@ enum Destination { #[serde(rename = "floor")] Floor, } + +// Tests + +use serde_json::to_string_pretty; + +#[test] +fn test_fill() { + let mut game = GameState::new(2).unwrap(); + + // println!("{}", to_string_pretty(&game).unwrap()); + assert!(game.only_start()); + + game.fill(); + //println!("{}", to_string_pretty(&game).unwrap()); + + assert_eq!(game.bag.len(), 100 - (5 * 4)); + assert_eq!(game.factories.iter().map(|f| f.len()).sum::(), 5 * 4); +}