azul: implement complete game iteration
This commit is contained in:
parent
14f268649a
commit
14a8cc1316
81
src/azul.rs
81
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<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
|
||||
|
|
Loading…
Reference in New Issue