From 14a8cc1316f0b2464705197fcc989d3b2bcc747f Mon Sep 17 00:00:00 2001 From: Daniel Olsen Date: Tue, 30 Jan 2024 01:49:59 +0100 Subject: [PATCH] azul: implement complete game iteration --- src/azul.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 10 deletions(-) diff --git a/src/azul.rs b/src/azul.rs index cc602c5..b417844 100644 --- a/src/azul.rs +++ b/src/azul.rs @@ -8,8 +8,15 @@ use rand::prelude::*; #[derive(Debug, Serialize, Deserialize, Clone)] struct GameState { + #[serde(skip)] + #[serde(default = "make_rng")] + rng: StdRng, n_players: usize, current_player: usize, + starting_player: usize, + game_end: bool, + rounds: usize, + days: usize, bag: TileSet, lid: TileSet, factories: Vec, @@ -17,6 +24,10 @@ struct GameState { players: Vec, } +fn make_rng() -> StdRng { + StdRng::from_entropy() +} + impl GameState { pub fn new(n_players: usize) -> Result { if n_players < 2 { @@ -45,8 +56,13 @@ impl GameState { } let game = GameState { + rng: StdRng::from_entropy(), n_players, current_player: 0, + starting_player: 0, + game_end: false, + rounds: 0, + days: 0, bag: TileSet { blue: 20, yellow: 20, @@ -73,12 +89,11 @@ impl GameState { /// Fills the factories from the bag /// Will replenish the bag from the lid when empty /// Will return with partially filled factories if out of tiles - fn fill(&mut self, mut rng: StdRng) -> Result<(), &'static str> { - for factory in &self.factories { - if factory.len() != 0 { - return Err("Cannot fill, factories are not empty"); - } + fn fill(&mut self) -> Result<(), &'static str> { + if !self.is_empty() { + return Err("Cannot fill, there are still tiles left to be picked"); } + for factory in &mut self.factories { for _ in 0..4 { if self.bag.is_empty() && !self.lid.is_empty() { @@ -103,19 +118,28 @@ impl GameState { ]; let dist = WeightedIndex::new(&weights).unwrap(); - let picked = choices[dist.sample(&mut rng)]; + let picked = choices[dist.sample(&mut self.rng)]; self.bag.add_color(picked, 1)?; factory.add_color(picked, 1)?; } } } + Ok(()) } + /// Returns whether or not the market and factories are empty + fn is_empty(&self) -> bool { + let factories = self.factories.iter().all(|f| f.is_empty()); + let market = self.market.is_empty(); + + factories && market + } + /// Scores the game and clears the boards into the lid /// Doesn't check if game is ready to be scored! - fn score(&mut self) -> Result<(), &'static str> { + fn score_unchecked(&mut self) -> Result<(), &'static str> { for i in 0..self.players.len() { let player = &mut self.players[i]; @@ -146,7 +170,7 @@ impl GameState { player.points -= negative; if player.floor.start == 1 { - self.current_player = i; + self.starting_player = i; player.floor.start = 0; } @@ -157,6 +181,43 @@ impl GameState { Ok(()) } + /// Manages the entire lifecycle of the game + /// After a move, will check if game should be scored, + /// After scoring, either ends the game and calculates bonus + /// or fills the factories again and sets the starting player + /// Also tracks rounds and days + fn do_iter(&mut self, game_move: GameMove) -> Result<(), MoveErr> { + if self.game_end { + return Err(MoveErr::Other("Game is over")); + } + + self.do_move(game_move).map_err(|e| e)?; + + if self.is_empty() { + self.score_unchecked().map_err(|e| MoveErr::Other(e))?; + self.game_end = self + .players + .iter() + .any(|p| p.wall.iter().any(|r| r.iter().all(|&v| v))); + + if self.game_end { + for i in 0..self.n_players { + self.players[i].points += self.players[i] + .bonus_score() + .map_err(|e| MoveErr::Other(e))?; + } + } else { + self.fill().map_err(|e| MoveErr::Other(e))?; + self.current_player = self.starting_player; + self.days += 1; + } + } + + self.rounds += 1; + + Ok(()) + } + /// Does a move according to the policy defined in the move fn do_move(&mut self, game_move: GameMove) -> Result<(), MoveErr> { match game_move.policy { @@ -494,7 +555,7 @@ impl From for TileSetWithStart { #[derive(Debug, Serialize, Deserialize, Clone, Default)] struct Player { - points: u8, + points: usize, pattern_lines: [PatternLine; 5], wall: Wall, floor: TileSetWithStart, @@ -538,7 +599,7 @@ impl Player { } /// given a coordinate on the board, returns how many tiles are in its group - fn connected(&self, row: usize, column: usize) -> u8 { + fn connected(&self, row: usize, column: usize) -> usize { // This implementation scans for contigious runs of tiles // until it finds the group the tile we're checking is part of // Does this for both the row and the column