diff --git a/src/azul.rs b/src/azul.rs index c5ca759..98f09c9 100644 --- a/src/azul.rs +++ b/src/azul.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::convert::TryFrom; use std::ops::{Add, AddAssign}; @@ -9,8 +10,9 @@ use rand::prelude::*; #[derive(Serialize, Deserialize)] pub struct GameState { n_players: usize, - current_player: usize, - starting_player: usize, + current_player: PlayerName, + starting_player: PlayerName, + player_names: Vec, game_end: bool, rounds: usize, days: usize, @@ -18,12 +20,27 @@ pub struct GameState { lid: TileSet, factories: Vec, market: TileSetWithStart, - players: Vec, + players: HashMap, #[serde(skip)] #[serde(default = "make_rng")] rng: Box, } +#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Hash)] +pub struct PlayerName(String); + +impl From for PlayerName { + fn from(value: String) -> Self { + Self(value) + } +} + +impl From<&str> for PlayerName { + fn from(value: &str) -> Self { + value.to_string().into() + } +} + fn make_rng() -> Box { Box::new(StdRng::from_entropy()) } @@ -32,8 +49,9 @@ impl Clone for GameState { fn clone(&self) -> Self { GameState { n_players: self.n_players, - current_player: self.current_player, - starting_player: self.starting_player, + current_player: self.current_player.clone(), + starting_player: self.starting_player.clone(), + player_names: self.player_names.clone(), game_end: self.game_end, rounds: self.rounds, days: self.days, @@ -48,7 +66,8 @@ impl Clone for GameState { } impl GameState { - pub fn new(n_players: usize) -> Result { + pub fn new(mut player_names: Vec) -> Result { + let n_players = player_names.len(); if n_players < 2 { return Err("Can't create game with less than two players"); } @@ -68,16 +87,21 @@ impl GameState { factories.push(TileSet::default()); } - let mut players = Vec::with_capacity(n_players); + let mut players = HashMap::::new(); - for _ in 0..n_players { - players.push(Player::default()) + for player in &player_names { + players.insert(player.clone(), Player::default()); } + let mut rng = Box::new(StdRng::from_entropy()); + player_names.shuffle(&mut rng); + let starting_player = player_names[0].to_owned(); + let game = GameState { n_players, - current_player: 0, - starting_player: 0, + current_player: starting_player.clone(), + starting_player, + player_names, game_end: false, rounds: 0, days: 0, @@ -99,7 +123,7 @@ impl GameState { white: 0, }, players, - rng: Box::new(StdRng::from_entropy()), + rng, }; Ok(game) @@ -170,9 +194,7 @@ impl GameState { /// Scores the game and clears the boards into the lid /// Doesn't check if game is ready to be scored! fn score_unchecked(&mut self) -> Result<(), &'static str> { - for i in 0..self.players.len() { - let player = &mut self.players[i]; - + for (player_name, player) in self.players.iter_mut() { for row in 0..5 { if player.pattern_lines[row].len() == (row + 1) { let color = player.pattern_lines[row] @@ -200,7 +222,7 @@ impl GameState { player.points -= negative; if player.floor.start == 1 { - self.starting_player = i; + self.starting_player = player_name.clone(); player.floor.start = 0; } @@ -227,18 +249,16 @@ impl GameState { self.score_unchecked().map_err(|e| MoveErr::Other(e))?; self.game_end = self .players - .iter() + .values() .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))?; + for player in self.players.values_mut() { + player.points += player.bonus_score().map_err(|e| MoveErr::Other(e))?; } } else { self.fill().map_err(|e| MoveErr::Other(e))?; - self.current_player = self.starting_player; + self.current_player = self.starting_player.clone(); self.days += 1; } } @@ -258,10 +278,10 @@ impl GameState { /// 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) { + match self.do_move_strict(game_move.clone()) { Ok(()) => Ok(()), Err(MoveErr::Dst(_)) => { - let mut new_game_move = game_move.clone(); + let mut new_game_move = game_move; new_game_move.destination = Destination::Floor; self.do_move_strict(new_game_move) } @@ -271,9 +291,7 @@ impl GameState { /// 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 { + if self.current_player != game_move.player { return Err(MoveErr::Player("Not this player's turn")); } @@ -294,13 +312,13 @@ impl GameState { return Err(MoveErr::Src("Not a valid factory")) } (_, _, Destination::PatternLine(l)) - if self.players[current_player].pattern_lines[l].number > l + 1 => + if self.players[&self.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] + if self.players[&self.current_player].pattern_lines[l].color != Some(c) + && !self.players[&self.current_player].pattern_lines[l] .color .is_none() => { @@ -309,7 +327,7 @@ impl GameState { )) } (c, _, Destination::PatternLine(l)) - if self.players[current_player].wall[l][Player::get_wall_index(l, c).unwrap()] + if self.players[&self.current_player].wall[l][Player::get_wall_index(l, c).unwrap()] == true => { return Err(MoveErr::Dst( @@ -319,28 +337,35 @@ impl GameState { (c, s, Destination::PatternLine(p)) => { let amount = self.take_tiles(s, c); - let pattern_line = &mut self.players[current_player].pattern_lines[p]; + let pattern_line = &mut self.players.get_mut(&self.current_player).unwrap().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] + self.players.get_mut(&self.current_player).unwrap() .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] + self.players.get_mut(&self.current_player).unwrap() .floor .add_color(c, amount as isize) .map_err(|e| MoveErr::Other(e))?; } }; - self.current_player = current_player + 1 % self.n_players; + self.current_player = self + .player_names + .iter() + .cycle() + .skip_while(|&a| a != &self.current_player) + .next() + .unwrap() + .to_owned(); Ok(()) } @@ -355,7 +380,7 @@ impl GameState { /// 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]; + let player = &mut self.players.get_mut(&self.current_player).unwrap(); // Sideffects match src { @@ -792,12 +817,12 @@ impl Iterator for ColorIter { type Row = [bool; 5]; type Wall = [Row; 5]; -#[derive(Serialize, Deserialize, Clone, Copy)] +#[derive(Serialize, Deserialize, Clone)] pub struct GameMove { - 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 + player: PlayerName, // 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 } @@ -837,7 +862,7 @@ use serde_json::to_string_pretty; #[test] fn test_fill() { - let mut game = GameState::new(2).unwrap(); + let mut game = GameState::new(vec!["Bot1".into(), "Bot2".into()]).unwrap(); // println!("{}", to_string_pretty(&game).unwrap()); assert!(game.only_start()); diff --git a/src/main.rs b/src/main.rs index e3d196b..d69775d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,11 +32,15 @@ fn get_game( #[post("/game", data = "")] fn new_game( - input: Option>, + input: Json, shared: &State, ) -> Result, &'static str> { let id = uuid::Uuid::new_v4(); - let game = azul::GameState::new(2)?; + + let game = match input.0 { + NewGameOptions::New { player_names } => azul::GameState::new(player_names)?, + NewGameOptions::Premade { game } => game, + }; let mut games = shared.games.lock().unwrap(); games.insert(id, game); @@ -61,13 +65,8 @@ fn make_move( #[derive(Serialize, Deserialize)] #[serde(untagged)] enum NewGameOptions { - New { - player_tokens: Vec, - }, - Premade { - player_tokens: Vec, - game: azul::GameState, - }, + New { player_names: Vec }, + Premade { game: azul::GameState }, } #[launch]