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)]
|
#[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
|
||||||
|
|
Loading…
Reference in New Issue