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::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<PlayerName>,
game_end: bool,
rounds: usize,
days: usize,
@ -18,12 +20,27 @@ pub struct GameState {
lid: TileSet,
factories: Vec<TileSet>,
market: TileSetWithStart,
players: Vec<Player>,
players: HashMap<PlayerName, Player>,
#[serde(skip)]
#[serde(default = "make_rng")]
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> {
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<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 {
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::<PlayerName, Player>::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());

View File

@ -32,11 +32,15 @@ fn get_game(
#[post("/game", data = "<input>")]
fn new_game(
input: Option<Json<NewGameOptions>>,
input: Json<NewGameOptions>,
shared: &State<SharedState>,
) -> Result<Json<uuid::Uuid>, &'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<String>,
},
Premade {
player_tokens: Vec<String>,
game: azul::GameState,
},
New { player_names: Vec<azul::PlayerName> },
Premade { game: azul::GameState },
}
#[launch]