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)]
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<TileSet>,
@ -17,6 +24,10 @@ struct GameState {
players: Vec<Player>,
}
fn make_rng() -> StdRng {
StdRng::from_entropy()
}
impl GameState {
pub fn new(n_players: usize) -> Result<GameState, &'static str> {
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<TileSet> 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