Make player number a name, hook up creating premade games

Order is decided by a vector of names

Co-authored-by: Eirik Witterso <eirikwit@pvv.ntnu.no>
This commit is contained in:
Daniel Lovbrotte Olsen 2024-02-10 23:51:22 +01:00
parent 1368c97ab3
commit 20c3108389
2 changed files with 75 additions and 51 deletions

View File

@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::ops::{Add, AddAssign}; use std::ops::{Add, AddAssign};
@ -9,8 +10,9 @@ use rand::prelude::*;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct GameState { pub struct GameState {
n_players: usize, n_players: usize,
current_player: usize, current_player: PlayerName,
starting_player: usize, starting_player: PlayerName,
player_names: Vec<PlayerName>,
game_end: bool, game_end: bool,
rounds: usize, rounds: usize,
days: usize, days: usize,
@ -18,12 +20,27 @@ pub struct GameState {
lid: TileSet, lid: TileSet,
factories: Vec<TileSet>, factories: Vec<TileSet>,
market: TileSetWithStart, market: TileSetWithStart,
players: Vec<Player>, players: HashMap<PlayerName, Player>,
#[serde(skip)] #[serde(skip)]
#[serde(default = "make_rng")] #[serde(default = "make_rng")]
rng: Box<dyn RngCore + Send>, rng: Box<dyn RngCore + Send>,
} }
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
pub struct PlayerName(String);
impl From<String> 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<dyn RngCore + Send> { fn make_rng() -> Box<dyn RngCore + Send> {
Box::new(StdRng::from_entropy()) Box::new(StdRng::from_entropy())
} }
@ -32,8 +49,9 @@ impl Clone for GameState {
fn clone(&self) -> Self { fn clone(&self) -> Self {
GameState { GameState {
n_players: self.n_players, n_players: self.n_players,
current_player: self.current_player, current_player: self.current_player.clone(),
starting_player: self.starting_player, starting_player: self.starting_player.clone(),
player_names: self.player_names.clone(),
game_end: self.game_end, game_end: self.game_end,
rounds: self.rounds, rounds: self.rounds,
days: self.days, days: self.days,
@ -48,7 +66,8 @@ impl Clone for GameState {
} }
impl GameState { impl GameState {
pub fn new(n_players: usize) -> Result<GameState, &'static str> { pub fn new(mut player_names: Vec<PlayerName>) -> Result<GameState, &'static str> {
let n_players = player_names.len();
if n_players < 2 { if n_players < 2 {
return Err("Can't create game with less than two players"); return Err("Can't create game with less than two players");
} }
@ -68,16 +87,21 @@ impl GameState {
factories.push(TileSet::default()); factories.push(TileSet::default());
} }
let mut players = Vec::with_capacity(n_players); let mut players = HashMap::<PlayerName, Player>::new();
for _ in 0..n_players { for player in &player_names {
players.push(Player::default()) 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 { let game = GameState {
n_players, n_players,
current_player: 0, current_player: starting_player.clone(),
starting_player: 0, starting_player,
player_names,
game_end: false, game_end: false,
rounds: 0, rounds: 0,
days: 0, days: 0,
@ -99,7 +123,7 @@ impl GameState {
white: 0, white: 0,
}, },
players, players,
rng: Box::new(StdRng::from_entropy()), rng,
}; };
Ok(game) Ok(game)
@ -170,9 +194,7 @@ impl GameState {
/// 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_unchecked(&mut self) -> Result<(), &'static str> { fn score_unchecked(&mut self) -> Result<(), &'static str> {
for i in 0..self.players.len() { for (player_name, player) in self.players.iter_mut() {
let player = &mut self.players[i];
for row in 0..5 { for row in 0..5 {
if player.pattern_lines[row].len() == (row + 1) { if player.pattern_lines[row].len() == (row + 1) {
let color = player.pattern_lines[row] let color = player.pattern_lines[row]
@ -200,7 +222,7 @@ impl GameState {
player.points -= negative; player.points -= negative;
if player.floor.start == 1 { if player.floor.start == 1 {
self.starting_player = i; self.starting_player = player_name.clone();
player.floor.start = 0; player.floor.start = 0;
} }
@ -227,18 +249,16 @@ impl GameState {
self.score_unchecked().map_err(|e| MoveErr::Other(e))?; self.score_unchecked().map_err(|e| MoveErr::Other(e))?;
self.game_end = self self.game_end = self
.players .players
.iter() .values()
.any(|p| p.wall.iter().any(|r| r.iter().all(|&v| v))); .any(|p| p.wall.iter().any(|r| r.iter().all(|&v| v)));
if self.game_end { if self.game_end {
for i in 0..self.n_players { for player in self.players.values_mut() {
self.players[i].points += self.players[i] player.points += player.bonus_score().map_err(|e| MoveErr::Other(e))?;
.bonus_score()
.map_err(|e| MoveErr::Other(e))?;
} }
} else { } else {
self.fill().map_err(|e| MoveErr::Other(e))?; self.fill().map_err(|e| MoveErr::Other(e))?;
self.current_player = self.starting_player; self.current_player = self.starting_player.clone();
self.days += 1; 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 /// 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> { 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(()), Ok(()) => Ok(()),
Err(MoveErr::Dst(_)) => { Err(MoveErr::Dst(_)) => {
let mut new_game_move = game_move.clone(); let mut new_game_move = game_move;
new_game_move.destination = Destination::Floor; new_game_move.destination = Destination::Floor;
self.do_move_strict(new_game_move) 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 /// Does a move, errors out if the move is weird in any way
fn do_move_strict(&mut self, game_move: GameMove) -> Result<(), MoveErr> { fn do_move_strict(&mut self, game_move: GameMove) -> Result<(), MoveErr> {
let current_player = self.current_player; if self.current_player != game_move.player {
if current_player != game_move.player {
return Err(MoveErr::Player("Not this player's turn")); return Err(MoveErr::Player("Not this player's turn"));
} }
@ -294,13 +312,13 @@ impl GameState {
return Err(MoveErr::Src("Not a valid factory")) return Err(MoveErr::Src("Not a valid factory"))
} }
(_, _, Destination::PatternLine(l)) (_, _, 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!")) return Err(MoveErr::Dst("That PatternLine is full!"))
} }
(c, _, Destination::PatternLine(l)) (c, _, Destination::PatternLine(l))
if self.players[current_player].pattern_lines[l].color != Some(c) if self.players[&self.current_player].pattern_lines[l].color != Some(c)
&& !self.players[current_player].pattern_lines[l] && !self.players[&self.current_player].pattern_lines[l]
.color .color
.is_none() => .is_none() =>
{ {
@ -309,7 +327,7 @@ impl GameState {
)) ))
} }
(c, _, Destination::PatternLine(l)) (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 => == true =>
{ {
return Err(MoveErr::Dst( return Err(MoveErr::Dst(
@ -319,28 +337,35 @@ impl GameState {
(c, s, Destination::PatternLine(p)) => { (c, s, Destination::PatternLine(p)) => {
let amount = self.take_tiles(s, c); 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 remaining_capacity = p + 1 - pattern_line.len();
let to_line = usize::min(amount, remaining_capacity); let to_line = usize::min(amount, remaining_capacity);
let to_floor = amount - to_line; let to_floor = amount - to_line;
pattern_line.number += to_line; pattern_line.number += to_line;
self.players[current_player] self.players.get_mut(&self.current_player).unwrap()
.floor .floor
.add_color(c, to_floor as isize) .add_color(c, to_floor as isize)
.map_err(|e| MoveErr::Other(e))?; .map_err(|e| MoveErr::Other(e))?;
} }
(c, s, Destination::Floor) => { (c, s, Destination::Floor) => {
let amount = self.take_tiles(s, c); let amount = self.take_tiles(s, c);
self.players[self.current_player] self.players.get_mut(&self.current_player).unwrap()
.floor .floor
.add_color(c, amount as isize) .add_color(c, amount as isize)
.map_err(|e| MoveErr::Other(e))?; .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(()) Ok(())
} }
@ -355,7 +380,7 @@ impl GameState {
/// Takes tiles from a source and causes side-effects /// Takes tiles from a source and causes side-effects
fn take_tiles(&mut self, src: Source, color: Color) -> usize { fn take_tiles(&mut self, src: Source, color: Color) -> usize {
let amount = self.get_color(src, color); 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 // Sideffects
match src { match src {
@ -792,12 +817,12 @@ 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)] #[derive(Serialize, Deserialize, Clone)]
pub struct GameMove { pub struct GameMove {
player: usize, // Safeguard to check that the bot knows which player it is 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 policy: Policy, // What policy to run moves under, so bots can do less error-handling
color: Color, // What color to select color: Color, // What color to select
source: Source, // What source to move the tiles from source: Source, // What source to move the tiles from
destination: Destination, // Where to place the tiles destination: Destination, // Where to place the tiles
} }
@ -837,7 +862,7 @@ use serde_json::to_string_pretty;
#[test] #[test]
fn test_fill() { 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()); // println!("{}", to_string_pretty(&game).unwrap());
assert!(game.only_start()); assert!(game.only_start());

View File

@ -32,11 +32,15 @@ fn get_game(
#[post("/game", data = "<input>")] #[post("/game", data = "<input>")]
fn new_game( fn new_game(
input: Option<Json<NewGameOptions>>, input: Json<NewGameOptions>,
shared: &State<SharedState>, shared: &State<SharedState>,
) -> Result<Json<uuid::Uuid>, &'static str> { ) -> Result<Json<uuid::Uuid>, &'static str> {
let id = uuid::Uuid::new_v4(); 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(); let mut games = shared.games.lock().unwrap();
games.insert(id, game); games.insert(id, game);
@ -61,13 +65,8 @@ fn make_move(
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
#[serde(untagged)] #[serde(untagged)]
enum NewGameOptions { enum NewGameOptions {
New { New { player_names: Vec<azul::PlayerName> },
player_tokens: Vec<String>, Premade { game: azul::GameState },
},
Premade {
player_tokens: Vec<String>,
game: azul::GameState,
},
} }
#[launch] #[launch]