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:
parent
1368c97ab3
commit
20c3108389
109
src/azul.rs
109
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<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());
|
||||
|
|
17
src/main.rs
17
src/main.rs
|
@ -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]
|
||||
|
|
Loading…
Reference in New Issue