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
103
src/azul.rs
103
src/azul.rs
|
@ -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,9 +817,9 @@ 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
|
||||||
|
@ -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());
|
||||||
|
|
17
src/main.rs
17
src/main.rs
|
@ -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]
|
||||||
|
|
Loading…
Reference in New Issue