diff --git a/src/azul.rs b/src/azul.rs index 6bab061..cc602c5 100644 --- a/src/azul.rs +++ b/src/azul.rs @@ -8,6 +8,7 @@ use rand::prelude::*; #[derive(Debug, Serialize, Deserialize, Clone)] struct GameState { + n_players: usize, current_player: usize, bag: TileSet, lid: TileSet, @@ -44,6 +45,7 @@ impl GameState { } let game = GameState { + n_players, current_player: 0, bag: TileSet { blue: 20, @@ -53,7 +55,7 @@ impl GameState { white: 20, }, lid: TileSet::default(), - factories: factories, + factories, market: TileSetWithStart { start: 1, blue: 0, @@ -62,7 +64,7 @@ impl GameState { black: 0, white: 0, }, - players: players, + players, }; Ok(game) @@ -126,7 +128,7 @@ impl GameState { player.wall[row][index] = true; player.points += player.connected(row, index); - self.lid.add_color(color, row)?; + self.lid.add_color(color, row as isize)?; player.pattern_lines[row] = PatternLine::default() } } @@ -154,6 +156,142 @@ impl GameState { 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 { + Policy::Strict => self.do_move_strict(game_move), + Policy::Loose => self.do_move_loose(game_move), + } + } + + /// Does a move, if the move is placed where it can't fit, places it on the floor + fn do_move_loose(&mut self, game_move: GameMove) -> Result<(), MoveErr> { + match self.do_move_strict(game_move) { + Ok(()) => Ok(()), + Err(MoveErr::Dst(_)) => { + let mut new_game_move = game_move.clone(); + new_game_move.destination = Destination::Floor; + self.do_move_strict(new_game_move) + } + e => e, + } + } + + /// Does a move, errors out if the move is weird in any way + fn do_move_strict(&mut self, game_move: GameMove) -> Result<(), MoveErr> { + let current_player = self.current_player; + + if current_player != game_move.player { + return Err(MoveErr::Player("Not this player's turn")); + } + + // let player = &mut self.players[current_player]; + + let color = game_move.color; + let src = game_move.source; + let dst = game_move.destination; + + match (color, src, dst) { + (Color::Start, _, _) => { + return Err(MoveErr::Color("You can't take the start tile specifically")) + } + (c, s, _) if self.get_color(s, c) == 0 => { + return Err(MoveErr::Color("Source does not contain that color")) + } + (_, Source::Factory(f), _) if f >= self.factories.len() => { + return Err(MoveErr::Src("Not a valid factory")) + } + (_, _, Destination::PatternLine(l)) + if self.players[current_player].pattern_lines[l].number > l + 1 => + { + return Err(MoveErr::Dst("That PatternLine is full!")) + } + (c, _, Destination::PatternLine(l)) + if self.players[current_player].pattern_lines[l].color != Some(c) + && !self.players[current_player].pattern_lines[l] + .color + .is_none() => + { + return Err(MoveErr::Dst( + "That pattern line already contains tiles of a different color!", + )) + } + (c, _, Destination::PatternLine(l)) + if self.players[current_player].wall[l][Player::get_wall_index(l, c).unwrap()] + == true => + { + return Err(MoveErr::Dst( + "That pattern line and color is already filled on the wall", + )) + } + (c, s, Destination::PatternLine(p)) => { + let amount = self.take_tiles(s, c); + + let pattern_line = &mut self.players[current_player].pattern_lines[p]; + + let remaining_capacity = p + 1 - pattern_line.len(); + let to_line = usize::min(amount, remaining_capacity); + let to_floor = amount - to_line; + + pattern_line.number += to_line; + self.players[current_player] + .floor + .add_color(c, to_floor as isize) + .map_err(|e| MoveErr::Other(e))?; + } + (c, s, Destination::Floor) => { + let amount = self.take_tiles(s, c); + self.players[self.current_player] + .floor + .add_color(c, amount as isize) + .map_err(|e| MoveErr::Other(e))?; + } + }; + + self.current_player = current_player + 1 % self.n_players; + Ok(()) + } + + /// Gets how many tiles of a given color is in a source + fn get_color(&self, src: Source, color: Color) -> usize { + match src { + Source::Market => self.market.get_color(color), + Source::Factory(f) => self.factories[f].get_color(color), + } + } + + /// Takes tiles from a source and causes side-effects + fn take_tiles(&mut self, src: Source, color: Color) -> usize { + let amount = self.get_color(src, color); + let player = &mut self.players[self.current_player]; + + // Sideffects + match src { + Source::Market => { + if self.market.start == 1 { + self.market.start -= 1; + player.floor.start += 1; + } + } + Source::Factory(f) => { + let factory = &mut self.factories[f]; + factory.add_color(color, -(amount as isize)).unwrap(); + self.market += *factory; + factory.clear(); + } + }; + + return amount; + } +} + +enum MoveErr { + Player(&'static str), + Color(&'static str), + Src(&'static str), + Dst(&'static str), + Other(&'static str), } #[derive(Debug, Serialize, Deserialize, Clone, Default, Copy)] @@ -173,18 +311,43 @@ impl TileSet { self.len() == 0 } - fn add_color(&mut self, color: Color, n: usize) -> Result<(), &'static str> { + fn add_color(&mut self, color: Color, n: isize) -> Result<(), &'static str> { match color { - Color::Blue => self.blue += n, - Color::Yellow => self.yellow += n, - Color::Red => self.red += n, - Color::Black => self.black += n, - Color::White => self.white += n, + Color::Blue => { + self.blue = usize::checked_add_signed(self.blue, n).ok_or("would overflow!")? + } + Color::Yellow => { + self.blue = usize::checked_add_signed(self.yellow, n).ok_or("would overflow")? + } + Color::Red => { + self.blue = usize::checked_add_signed(self.red, n).ok_or("would overflow")? + } + Color::Black => { + self.blue = usize::checked_add_signed(self.black, n).ok_or("would overflow")? + } + Color::White => { + self.blue = usize::checked_add_signed(self.white, n).ok_or("would overflow")? + } Color::Start => return Err("tried to add Start tiles to TileSet"), } Ok(()) } + + fn get_color(&self, color: Color) -> usize { + match color { + Color::Start => 0, + Color::Blue => self.blue, + Color::Yellow => self.yellow, + Color::Red => self.red, + Color::Black => self.white, + Color::White => self.black, + } + } + + fn clear(&mut self) { + *self = Self::default(); + } } impl Add for TileSet { @@ -242,6 +405,91 @@ impl TileSetWithStart { fn is_empty(&self) -> bool { self.len() == 0 } + fn get_color(&self, color: Color) -> usize { + match color { + Color::Start => self.start, + Color::Blue => self.blue, + Color::Yellow => self.yellow, + Color::Red => self.red, + Color::Black => self.white, + Color::White => self.black, + } + } + + fn add_color(&mut self, color: Color, n: isize) -> Result<(), &'static str> { + match color { + Color::Blue => { + self.blue = usize::checked_add_signed(self.blue, n).ok_or("would overflow!")? + } + Color::Yellow => { + self.blue = usize::checked_add_signed(self.yellow, n).ok_or("would overflow")? + } + Color::Red => { + self.blue = usize::checked_add_signed(self.red, n).ok_or("would overflow")? + } + Color::Black => { + self.blue = usize::checked_add_signed(self.black, n).ok_or("would overflow")? + } + Color::White => { + self.blue = usize::checked_add_signed(self.white, n).ok_or("would overflow")? + } + Color::Start if n == 1 && self.start == 0 => { + self.start = usize::checked_add_signed(self.white, n).ok_or("would overflow")? + } + Color::Start => return Err("Tried to add more than 1 start tile to TileSet"), + } + + Ok(()) + } +} + +impl Add for TileSetWithStart { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { + start: self.start + other.start, + blue: self.blue + other.blue, + yellow: self.yellow + other.yellow, + red: self.red + other.red, + black: self.black + other.black, + white: self.white + other.white, + } + } +} + +impl Add for TileSetWithStart { + type Output = Self; + + fn add(self, other: TileSet) -> Self { + Self { + start: self.start, + blue: self.blue + other.blue, + yellow: self.yellow + other.yellow, + red: self.red + other.red, + black: self.black + other.black, + white: self.white + other.white, + } + } +} + +impl AddAssign for TileSetWithStart { + fn add_assign(&mut self, other: TileSet) { + *self = *self + other + } +} + +impl From for TileSetWithStart { + fn from(value: TileSet) -> Self { + TileSetWithStart { + start: 0, + blue: value.blue, + yellow: value.yellow, + red: value.red, + black: value.black, + white: value.white, + } + } } #[derive(Debug, Serialize, Deserialize, Clone, Default)] @@ -396,7 +644,7 @@ impl PatternLine { } } -#[derive(Debug, Serialize, Deserialize, Clone, Copy)] +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] enum Color { Start, Blue, @@ -453,10 +701,41 @@ impl Iterator for ColorIter { type Row = [bool; 5]; type Wall = [Row; 5]; +#[derive(Serialize, Deserialize, Clone, Copy)] struct GameMove { - player: u8, - market: bool, - factory: u8, - color: Color, - pattern_line: usize, + player: usize, // Safeguard to check that the bot knows which player it is + policy: Policy, // What policy to run moves under, so bots can do less error-handling + color: Color, // What color to select + source: Source, // What source to move the tiles from + destination: Destination, // Where to place the tiles +} + +#[derive(Serialize, Deserialize, Clone, Copy)] +#[serde(untagged)] +enum Policy { + /// Anything weird will return an error + #[serde(rename = "strict")] + Strict, + #[serde(rename = "loose")] + /// If you place tiles wrongly, they will fall on the floor, anything else will return an error + Loose, + // Anything weird will instead play a random move + // #[serde(rename = "random")] + // Random, +} + +#[derive(Serialize, Deserialize, Clone, Copy)] +#[serde(untagged)] +enum Source { + #[serde(rename = "market")] + Market, + Factory(usize), +} + +#[derive(Serialize, Deserialize, Clone, Copy)] +#[serde(untagged)] +enum Destination { + PatternLine(usize), + #[serde(rename = "floor")] + Floor, }