azul: implement doing a move
This commit is contained in:
parent
4470f87654
commit
14f268649a
309
src/azul.rs
309
src/azul.rs
|
@ -8,6 +8,7 @@ use rand::prelude::*;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
struct GameState {
|
struct GameState {
|
||||||
|
n_players: usize,
|
||||||
current_player: usize,
|
current_player: usize,
|
||||||
bag: TileSet,
|
bag: TileSet,
|
||||||
lid: TileSet,
|
lid: TileSet,
|
||||||
|
@ -44,6 +45,7 @@ impl GameState {
|
||||||
}
|
}
|
||||||
|
|
||||||
let game = GameState {
|
let game = GameState {
|
||||||
|
n_players,
|
||||||
current_player: 0,
|
current_player: 0,
|
||||||
bag: TileSet {
|
bag: TileSet {
|
||||||
blue: 20,
|
blue: 20,
|
||||||
|
@ -53,7 +55,7 @@ impl GameState {
|
||||||
white: 20,
|
white: 20,
|
||||||
},
|
},
|
||||||
lid: TileSet::default(),
|
lid: TileSet::default(),
|
||||||
factories: factories,
|
factories,
|
||||||
market: TileSetWithStart {
|
market: TileSetWithStart {
|
||||||
start: 1,
|
start: 1,
|
||||||
blue: 0,
|
blue: 0,
|
||||||
|
@ -62,7 +64,7 @@ impl GameState {
|
||||||
black: 0,
|
black: 0,
|
||||||
white: 0,
|
white: 0,
|
||||||
},
|
},
|
||||||
players: players,
|
players,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(game)
|
Ok(game)
|
||||||
|
@ -126,7 +128,7 @@ impl GameState {
|
||||||
player.wall[row][index] = true;
|
player.wall[row][index] = true;
|
||||||
player.points += player.connected(row, index);
|
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()
|
player.pattern_lines[row] = PatternLine::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,6 +156,142 @@ impl GameState {
|
||||||
|
|
||||||
Ok(())
|
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)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default, Copy)]
|
||||||
|
@ -173,18 +311,43 @@ impl TileSet {
|
||||||
self.len() == 0
|
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 {
|
match color {
|
||||||
Color::Blue => self.blue += n,
|
Color::Blue => {
|
||||||
Color::Yellow => self.yellow += n,
|
self.blue = usize::checked_add_signed(self.blue, n).ok_or("would overflow!")?
|
||||||
Color::Red => self.red += n,
|
}
|
||||||
Color::Black => self.black += n,
|
Color::Yellow => {
|
||||||
Color::White => self.white += n,
|
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"),
|
Color::Start => return Err("tried to add Start tiles to TileSet"),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
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 {
|
impl Add for TileSet {
|
||||||
|
@ -242,6 +405,91 @@ impl TileSetWithStart {
|
||||||
fn is_empty(&self) -> bool {
|
fn is_empty(&self) -> bool {
|
||||||
self.len() == 0
|
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<TileSet> 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<TileSet> for TileSetWithStart {
|
||||||
|
fn add_assign(&mut self, other: TileSet) {
|
||||||
|
*self = *self + other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TileSet> 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)]
|
#[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 {
|
enum Color {
|
||||||
Start,
|
Start,
|
||||||
Blue,
|
Blue,
|
||||||
|
@ -453,10 +701,41 @@ impl Iterator for ColorIter {
|
||||||
type Row = [bool; 5];
|
type Row = [bool; 5];
|
||||||
type Wall = [Row; 5];
|
type Wall = [Row; 5];
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Copy)]
|
||||||
struct GameMove {
|
struct GameMove {
|
||||||
player: u8,
|
player: usize, // Safeguard to check that the bot knows which player it is
|
||||||
market: bool,
|
policy: Policy, // What policy to run moves under, so bots can do less error-handling
|
||||||
factory: u8,
|
color: Color, // What color to select
|
||||||
color: Color,
|
source: Source, // What source to move the tiles from
|
||||||
pattern_line: usize,
|
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,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue