azul: implement complete game iteration

This commit is contained in:
Daniel Lovbrotte Olsen 2024-01-30 01:49:59 +01:00
parent 14f268649a
commit 14a8cc1316
1 changed files with 71 additions and 10 deletions

View File

@ -8,8 +8,15 @@ use rand::prelude::*;
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
struct GameState { struct GameState {
#[serde(skip)]
#[serde(default = "make_rng")]
rng: StdRng,
n_players: usize, n_players: usize,
current_player: usize, current_player: usize,
starting_player: usize,
game_end: bool,
rounds: usize,
days: usize,
bag: TileSet, bag: TileSet,
lid: TileSet, lid: TileSet,
factories: Vec<TileSet>, factories: Vec<TileSet>,
@ -17,6 +24,10 @@ struct GameState {
players: Vec<Player>, players: Vec<Player>,
} }
fn make_rng() -> StdRng {
StdRng::from_entropy()
}
impl GameState { impl GameState {
pub fn new(n_players: usize) -> Result<GameState, &'static str> { pub fn new(n_players: usize) -> Result<GameState, &'static str> {
if n_players < 2 { if n_players < 2 {
@ -45,8 +56,13 @@ impl GameState {
} }
let game = GameState { let game = GameState {
rng: StdRng::from_entropy(),
n_players, n_players,
current_player: 0, current_player: 0,
starting_player: 0,
game_end: false,
rounds: 0,
days: 0,
bag: TileSet { bag: TileSet {
blue: 20, blue: 20,
yellow: 20, yellow: 20,
@ -73,12 +89,11 @@ impl GameState {
/// Fills the factories from the bag /// Fills the factories from the bag
/// Will replenish the bag from the lid when empty /// Will replenish the bag from the lid when empty
/// Will return with partially filled factories if out of tiles /// Will return with partially filled factories if out of tiles
fn fill(&mut self, mut rng: StdRng) -> Result<(), &'static str> { fn fill(&mut self) -> Result<(), &'static str> {
for factory in &self.factories { if !self.is_empty() {
if factory.len() != 0 { return Err("Cannot fill, there are still tiles left to be picked");
return Err("Cannot fill, factories are not empty");
}
} }
for factory in &mut self.factories { for factory in &mut self.factories {
for _ in 0..4 { for _ in 0..4 {
if self.bag.is_empty() && !self.lid.is_empty() { if self.bag.is_empty() && !self.lid.is_empty() {
@ -103,19 +118,28 @@ impl GameState {
]; ];
let dist = WeightedIndex::new(&weights).unwrap(); 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)?; self.bag.add_color(picked, 1)?;
factory.add_color(picked, 1)?; factory.add_color(picked, 1)?;
} }
} }
} }
Ok(()) 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 /// Scores the game and clears the boards into the lid
/// Doesn't check if game is ready to be scored! /// 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() { for i in 0..self.players.len() {
let player = &mut self.players[i]; let player = &mut self.players[i];
@ -146,7 +170,7 @@ impl GameState {
player.points -= negative; player.points -= negative;
if player.floor.start == 1 { if player.floor.start == 1 {
self.current_player = i; self.starting_player = i;
player.floor.start = 0; player.floor.start = 0;
} }
@ -157,6 +181,43 @@ impl GameState {
Ok(()) 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 /// Does a move according to the policy defined in the move
fn do_move(&mut self, game_move: GameMove) -> Result<(), MoveErr> { fn do_move(&mut self, game_move: GameMove) -> Result<(), MoveErr> {
match game_move.policy { match game_move.policy {
@ -494,7 +555,7 @@ impl From<TileSet> for TileSetWithStart {
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default)]
struct Player { struct Player {
points: u8, points: usize,
pattern_lines: [PatternLine; 5], pattern_lines: [PatternLine; 5],
wall: Wall, wall: Wall,
floor: TileSetWithStart, floor: TileSetWithStart,
@ -538,7 +599,7 @@ impl Player {
} }
/// given a coordinate on the board, returns how many tiles are in its group /// 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 // This implementation scans for contigious runs of tiles
// until it finds the group the tile we're checking is part of // until it finds the group the tile we're checking is part of
// Does this for both the row and the column // Does this for both the row and the column