implement planetwars, for the most part
This commit is contained in:
parent
9c696fa89b
commit
0e9e36c0c8
9 changed files with 170 additions and 512 deletions
|
@ -11,3 +11,6 @@ mozaic = { git = "https://github.com/ajuvercr/MOZAIC" }
|
||||||
tokio = "0.1.22"
|
tokio = "0.1.22"
|
||||||
rand = { version = "0.6.5", default-features = true }
|
rand = { version = "0.6.5", default-features = true }
|
||||||
futures = "0.1.28"
|
futures = "0.1.28"
|
||||||
|
serde = "1.0.100"
|
||||||
|
serde_derive = "1.0.100"
|
||||||
|
serde_json = "1.0"
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
extern crate serde;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
extern crate serde_json;
|
||||||
|
|
||||||
extern crate tokio;
|
extern crate tokio;
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
|
@ -11,6 +15,9 @@ use mozaic::errors;
|
||||||
|
|
||||||
use mozaic::modules::{Aggregator, Steplock, game};
|
use mozaic::modules::{Aggregator, Steplock, game};
|
||||||
|
|
||||||
|
|
||||||
|
mod planetwars;
|
||||||
|
|
||||||
// Load the config and start the game.
|
// Load the config and start the game.
|
||||||
fn main() {
|
fn main() {
|
||||||
run(env::args().collect());
|
run(env::args().collect());
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
|
||||||
|
use mozaic::modules::game;
|
||||||
|
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
mod pw_config;
|
||||||
|
mod pw_serializer;
|
||||||
|
mod pw_rules;
|
||||||
|
mod pw_protocol;
|
||||||
|
use pw_protocol::{ self as proto, CommandError };
|
||||||
|
use pw_rules::Dispatch;
|
||||||
|
|
||||||
|
|
||||||
|
pub struct PlanetWarsGame {
|
||||||
|
state: pw_rules::PlanetWars,
|
||||||
|
planet_map: HashMap<String, usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlanetWarsGame {
|
||||||
|
|
||||||
|
fn dispatch_state(&self, updates: &mut Vec<game::Update>, ) {
|
||||||
|
let state = pw_serializer::serialize(&self.state);
|
||||||
|
println!("{}", serde_json::to_string(&state).unwrap());
|
||||||
|
|
||||||
|
for player in self.state.players.iter() {
|
||||||
|
let state = pw_serializer::serialize_rotated(&self.state, player.id);
|
||||||
|
let state = if player.alive {
|
||||||
|
proto::ServerMessage::GameState(state)
|
||||||
|
} else {
|
||||||
|
proto::ServerMessage::FinalState(state)
|
||||||
|
};
|
||||||
|
|
||||||
|
updates.push(
|
||||||
|
game::Update::Player((player.id as u64).into(), serde_json::to_vec(&state).unwrap())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_commands<'a>(&mut self, turns: Vec<game::PlayerTurn<'a>>, updates: &mut Vec<game::Update>) {
|
||||||
|
for (player_id, command) in turns.into_iter() {
|
||||||
|
let player_num: usize = (*player_id).try_into().unwrap();
|
||||||
|
let action = proto::ServerMessage::PlayerAction(self.execute_action(player_num, command));
|
||||||
|
let serialized_action = serde_json::to_vec(&action).unwrap();
|
||||||
|
updates.push(game::Update::Player(player_id, serialized_action));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_action<'a>(&mut self, player_num: usize, turn: game::Turn<'a>) -> proto::PlayerAction {
|
||||||
|
let turn = match turn {
|
||||||
|
game::Turn::Timeout => return proto::PlayerAction::Timeout,
|
||||||
|
game::Turn::Action(bytes) => bytes,
|
||||||
|
};
|
||||||
|
|
||||||
|
let action: proto::Action = match serde_json::from_slice(&turn) {
|
||||||
|
Err(err) => return proto::PlayerAction::ParseError(err.to_string()),
|
||||||
|
Ok(action) => action,
|
||||||
|
};
|
||||||
|
|
||||||
|
let commands = action.commands.into_iter().map(|command| {
|
||||||
|
match self.check_valid_command(player_num, &command) {
|
||||||
|
Ok(dispatch) => {
|
||||||
|
self.state.dispatch(&dispatch);
|
||||||
|
proto::PlayerCommand {
|
||||||
|
command,
|
||||||
|
error: None,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(error) => {
|
||||||
|
proto::PlayerCommand {
|
||||||
|
command,
|
||||||
|
error: Some(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
return proto::PlayerAction::Commands(commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_valid_command(&self, player_num: usize, mv: &proto::Command) -> Result<Dispatch, CommandError> {
|
||||||
|
let origin_id = *self.planet_map
|
||||||
|
.get(&mv.origin)
|
||||||
|
.ok_or(CommandError::OriginDoesNotExist)?;
|
||||||
|
|
||||||
|
let target_id = *self.planet_map
|
||||||
|
.get(&mv.destination)
|
||||||
|
.ok_or(CommandError::DestinationDoesNotExist)?;
|
||||||
|
|
||||||
|
if self.state.planets[origin_id].owner() != Some(player_num) {
|
||||||
|
return Err(CommandError::OriginNotOwned);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.state.planets[origin_id].ship_count() < mv.ship_count {
|
||||||
|
return Err(CommandError::NotEnoughShips);
|
||||||
|
}
|
||||||
|
|
||||||
|
if mv.ship_count == 0 {
|
||||||
|
return Err(CommandError::ZeroShipMove);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Dispatch {
|
||||||
|
origin: origin_id,
|
||||||
|
target: target_id,
|
||||||
|
ship_count: mv.ship_count,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl game::GameController for PlanetWarsGame {
|
||||||
|
fn step<'a>(&mut self, turns: Vec<game::PlayerTurn<'a>>) -> Vec<game::Update> {
|
||||||
|
let mut updates = Vec::new();
|
||||||
|
|
||||||
|
self.state.repopulate();
|
||||||
|
self.execute_commands(turns, &mut updates);
|
||||||
|
self.state.step();
|
||||||
|
|
||||||
|
self.dispatch_state(&mut updates);
|
||||||
|
|
||||||
|
updates
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,10 +7,6 @@ use serde_json;
|
||||||
use super::pw_protocol as proto;
|
use super::pw_protocol as proto;
|
||||||
use super::pw_rules::*;
|
use super::pw_rules::*;
|
||||||
|
|
||||||
// TODO
|
|
||||||
use server::ClientId;
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub map_file: String,
|
pub map_file: String,
|
||||||
|
@ -18,7 +14,7 @@ pub struct Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn create_game(&self, clients: Vec<ClientId>) -> PlanetWars {
|
pub fn create_game(&self, clients: Vec<usize>) -> PlanetWars {
|
||||||
let planets = self.load_map(clients.len());
|
let planets = self.load_map(clients.len());
|
||||||
let players = clients.into_iter()
|
let players = clients.into_iter()
|
||||||
.map(|client_id| Player { id: client_id, alive: true })
|
.map(|client_id| Player { id: client_id, alive: true })
|
||||||
|
@ -45,7 +41,7 @@ impl Config {
|
||||||
let owner = planet.owner.and_then(|owner_num| {
|
let owner = planet.owner.and_then(|owner_num| {
|
||||||
// in the current map format, player numbers start at 1.
|
// in the current map format, player numbers start at 1.
|
||||||
// TODO: we might want to change this.
|
// TODO: we might want to change this.
|
||||||
let player_num = owner_num as usize - 1;
|
let player_num = owner_num - 1;
|
||||||
// ignore players that are not in the game
|
// ignore players that are not in the game
|
||||||
if player_num < num_players {
|
if player_num < num_players {
|
||||||
Some(player_num)
|
Some(player_num)
|
||||||
|
@ -69,7 +65,7 @@ impl Config {
|
||||||
}).collect();
|
}).collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_map(&self) -> io::Result<proto::Map> {
|
fn read_map(&self) -> io::Result<Map> {
|
||||||
let mut file = File::open(&self.map_file)?;
|
let mut file = File::open(&self.map_file)?;
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
file.read_to_string(&mut buf)?;
|
file.read_to_string(&mut buf)?;
|
||||||
|
@ -77,3 +73,8 @@ impl Config {
|
||||||
return Ok(map);
|
return Ok(map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Map {
|
||||||
|
pub planets: Vec<proto::Planet>,
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ pub struct Expedition {
|
||||||
pub ship_count: u64,
|
pub ship_count: u64,
|
||||||
pub origin: String,
|
pub origin: String,
|
||||||
pub destination: String,
|
pub destination: String,
|
||||||
pub owner: u32,
|
pub owner: usize,
|
||||||
pub turns_remaining: u64,
|
pub turns_remaining: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ pub struct Planet {
|
||||||
pub ship_count: u64,
|
pub ship_count: u64,
|
||||||
pub x: f64,
|
pub x: f64,
|
||||||
pub y: f64,
|
pub y: f64,
|
||||||
pub owner: Option<u32>,
|
pub owner: Option<usize>,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,11 +30,6 @@ pub struct Command {
|
||||||
pub ship_count: u64,
|
pub ship_count: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct Map {
|
|
||||||
pub planets: Vec<Planet>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
pub planets: Vec<Planet>,
|
pub planets: Vec<Planet>,
|
||||||
|
@ -82,24 +77,3 @@ pub enum ServerMessage {
|
||||||
/// The game is over, and this is the concluding state.
|
/// The game is over, and this is the concluding state.
|
||||||
FinalState(State),
|
FinalState(State),
|
||||||
}
|
}
|
||||||
|
|
||||||
// lobby messages
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
#[serde(tag = "type", content = "content")]
|
|
||||||
pub enum ControlMessage {
|
|
||||||
PlayerConnected {
|
|
||||||
player_id: u64,
|
|
||||||
},
|
|
||||||
PlayerDisconnected {
|
|
||||||
player_id: u64,
|
|
||||||
},
|
|
||||||
GameState(State),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
#[serde(tag = "type", content = "content")]
|
|
||||||
pub enum LobbyCommand {
|
|
||||||
StartMatch,
|
|
||||||
}
|
|
|
@ -1,4 +1,3 @@
|
||||||
use server::ClientId;
|
|
||||||
|
|
||||||
/// The planet wars game rules.
|
/// The planet wars game rules.
|
||||||
pub struct PlanetWars {
|
pub struct PlanetWars {
|
||||||
|
@ -14,7 +13,7 @@ pub struct PlanetWars {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
pub id: ClientId,
|
pub id: usize,
|
||||||
pub alive: bool,
|
pub alive: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +135,7 @@ impl PlanetWars {
|
||||||
return remaining < 2 || self.turn_num >= self.max_turns;
|
return remaining < 2 || self.turn_num >= self.max_turns;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn living_players(&self) -> Vec<ClientId> {
|
pub fn living_players(&self) -> Vec<usize> {
|
||||||
self.players.iter().filter_map(|p| {
|
self.players.iter().filter_map(|p| {
|
||||||
if p.alive {
|
if p.alive {
|
||||||
Some(p.id)
|
Some(p.id)
|
||||||
|
@ -197,4 +196,4 @@ impl Planet {
|
||||||
let dy = self.y - other.y;
|
let dy = self.y - other.y;
|
||||||
return (dx.powi(2) + dy.powi(2)).sqrt().ceil() as u64;
|
return (dx.powi(2) + dy.powi(2)).sqrt().ceil() as u64;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,20 +1,28 @@
|
||||||
|
|
||||||
use super::pw_rules::{PlanetWars, Planet, Expedition};
|
use super::pw_rules::{PlanetWars, Planet, Expedition};
|
||||||
use super::pw_protocol as proto;
|
use super::pw_protocol as proto;
|
||||||
|
|
||||||
/// Serialize given gamestate
|
/// Serialize given gamestate
|
||||||
pub fn serialize_state(state: &PlanetWars) -> proto::State {
|
pub fn serialize(state: &PlanetWars) -> proto::State {
|
||||||
let serializer = Serializer::new(state);
|
serialize_rotated(state, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize given gamestate with player numbers rotated by given offset.
|
||||||
|
pub fn serialize_rotated(state: &PlanetWars, offset: usize) -> proto::State {
|
||||||
|
let serializer = Serializer::new(state, offset);
|
||||||
serializer.serialize_state()
|
serializer.serialize_state()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Serializer<'a> {
|
struct Serializer<'a> {
|
||||||
state: &'a PlanetWars,
|
state: &'a PlanetWars,
|
||||||
|
player_num_offset: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Serializer<'a> {
|
impl<'a> Serializer<'a> {
|
||||||
fn new(state: &'a PlanetWars) -> Self {
|
fn new(state: &'a PlanetWars, offset: usize) -> Self {
|
||||||
Serializer {
|
Serializer {
|
||||||
state: state,
|
state: state,
|
||||||
|
player_num_offset: offset,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,9 +41,14 @@ impl<'a> Serializer<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// gets the client id for a player number
|
/// Gets the player number for given player id.
|
||||||
fn player_client_id(&self, player_num: usize) -> u32 {
|
/// Player numbers are 1-based (as opposed to player ids), They will also be
|
||||||
self.state.players[player_num].id.as_u32()
|
/// rotated based on the number offset for this serializer.
|
||||||
|
fn player_num(&self, player_id: usize) -> usize {
|
||||||
|
let num_players = self.state.players.len();
|
||||||
|
let rotated_id = (player_id + self.player_num_offset) % num_players;
|
||||||
|
// protocol player ids start at 1
|
||||||
|
return rotated_id + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_planet(&self, planet: &Planet) -> proto::Planet {
|
fn serialize_planet(&self, planet: &Planet) -> proto::Planet {
|
||||||
|
@ -43,7 +56,7 @@ impl<'a> Serializer<'a> {
|
||||||
name: planet.name.clone(),
|
name: planet.name.clone(),
|
||||||
x: planet.x,
|
x: planet.x,
|
||||||
y: planet.y,
|
y: planet.y,
|
||||||
owner: planet.owner().map(|num| self.player_client_id(num)),
|
owner: planet.owner().map(|id| self.player_num(id)),
|
||||||
ship_count: planet.ship_count(),
|
ship_count: planet.ship_count(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,10 +64,10 @@ impl<'a> Serializer<'a> {
|
||||||
fn serialize_expedition(&self, exp: &Expedition) -> proto::Expedition {
|
fn serialize_expedition(&self, exp: &Expedition) -> proto::Expedition {
|
||||||
proto::Expedition {
|
proto::Expedition {
|
||||||
id: exp.id,
|
id: exp.id,
|
||||||
owner: self.player_client_id(exp.fleet.owner.unwrap()),
|
owner: self.player_num(exp.fleet.owner.unwrap()),
|
||||||
ship_count: exp.fleet.ship_count,
|
ship_count: exp.fleet.ship_count,
|
||||||
origin: self.state.planets[exp.origin].name.clone(),
|
origin: self.state.planets[exp.origin as usize].name.clone(),
|
||||||
destination: self.state.planets[exp.target].name.clone(),
|
destination: self.state.planets[exp.target as usize].name.clone(),
|
||||||
turns_remaining: exp.turns_remaining,
|
turns_remaining: exp.turns_remaining,
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,11 +0,0 @@
|
||||||
|
|
||||||
pub mod pw_protocol;
|
|
||||||
mod pw_controller;
|
|
||||||
mod pw_rules;
|
|
||||||
mod pw_config;
|
|
||||||
mod pw_serializer;
|
|
||||||
|
|
||||||
pub use self::pw_controller::PwMatch;
|
|
||||||
pub use self::pw_rules::PlanetWars;
|
|
||||||
pub use self::pw_config::Config;
|
|
||||||
pub use self::pw_protocol::Map;
|
|
|
@ -1,452 +0,0 @@
|
||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
use std::mem;
|
|
||||||
use std::io;
|
|
||||||
|
|
||||||
use events;
|
|
||||||
use network::server::RegisteredHandle;
|
|
||||||
use reactors::reactor::ReactorHandle;
|
|
||||||
use reactors::{WireEvent, RequestHandler};
|
|
||||||
use server::{ClientId, ConnectionManager};
|
|
||||||
use sodiumoxide::crypto::sign::PublicKey;
|
|
||||||
|
|
||||||
use super::Config;
|
|
||||||
use super::pw_rules::{PlanetWars, Dispatch};
|
|
||||||
use super::pw_serializer::serialize_state;
|
|
||||||
use super::pw_protocol::{
|
|
||||||
self as proto,
|
|
||||||
PlayerAction,
|
|
||||||
PlayerCommand,
|
|
||||||
CommandError,
|
|
||||||
};
|
|
||||||
|
|
||||||
use serde_json;
|
|
||||||
|
|
||||||
pub struct Player {
|
|
||||||
id: ClientId,
|
|
||||||
num: usize,
|
|
||||||
handle: RegisteredHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ClientHandler {
|
|
||||||
client_id: u32,
|
|
||||||
reactor_handle: ReactorHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ClientHandler {
|
|
||||||
pub fn new(client_id: u32, reactor_handle: ReactorHandle) -> Self {
|
|
||||||
ClientHandler {
|
|
||||||
client_id,
|
|
||||||
reactor_handle,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_connect(&mut self, _event: &events::Connected)
|
|
||||||
-> io::Result<WireEvent>
|
|
||||||
{
|
|
||||||
self.reactor_handle.dispatch(events::ClientConnected {
|
|
||||||
client_id: self.client_id,
|
|
||||||
});
|
|
||||||
Ok(WireEvent::null())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_disconnect(&mut self, _event: &events::Disconnected)
|
|
||||||
-> io::Result<WireEvent>
|
|
||||||
{
|
|
||||||
self.reactor_handle.dispatch(events::ClientDisconnected {
|
|
||||||
client_id: self.client_id,
|
|
||||||
});
|
|
||||||
Ok(WireEvent::null())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_message(&mut self, event: &events::ClientSend)
|
|
||||||
-> io::Result<WireEvent>
|
|
||||||
{
|
|
||||||
self.reactor_handle.dispatch(events::ClientMessage {
|
|
||||||
client_id: self.client_id,
|
|
||||||
data: event.data.clone(),
|
|
||||||
});
|
|
||||||
Ok(WireEvent::null())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PwMatch {
|
|
||||||
state: PwMatchState,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum PwMatchState {
|
|
||||||
Lobby(Lobby),
|
|
||||||
Playing(PwController),
|
|
||||||
Finished,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PwMatch {
|
|
||||||
pub fn new(match_uuid: Vec<u8>,
|
|
||||||
reactor_handle: ReactorHandle,
|
|
||||||
connection_manager: ConnectionManager)
|
|
||||||
-> Self
|
|
||||||
{
|
|
||||||
let lobby = Lobby::new(
|
|
||||||
match_uuid,
|
|
||||||
connection_manager,
|
|
||||||
reactor_handle
|
|
||||||
);
|
|
||||||
|
|
||||||
return PwMatch {
|
|
||||||
state: PwMatchState::Lobby(lobby),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn take_state(&mut self) -> PwMatchState {
|
|
||||||
mem::replace(&mut self.state, PwMatchState::Finished)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register_client(&mut self, event: &events::RegisterClient) {
|
|
||||||
if let &mut PwMatchState::Lobby(ref mut lobby) = &mut self.state {
|
|
||||||
let client_id = ClientId::new(event.client_id);
|
|
||||||
let key = PublicKey::from_slice(&event.public_key).unwrap();
|
|
||||||
lobby.add_player(client_id, key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_client(&mut self, event: &events::RemoveClient) {
|
|
||||||
if let PwMatchState::Lobby(ref mut lobby) = self.state {
|
|
||||||
let client_id = ClientId::new(event.client_id);
|
|
||||||
lobby.remove_player(client_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start_game(&mut self, event: &events::StartGame) {
|
|
||||||
let state = self.take_state();
|
|
||||||
|
|
||||||
if let PwMatchState::Lobby(lobby) = state {
|
|
||||||
let config = Config {
|
|
||||||
map_file: event.map_path.clone(),
|
|
||||||
max_turns: event.max_turns as u64,
|
|
||||||
};
|
|
||||||
self.state = PwMatchState::Playing(PwController::new(
|
|
||||||
config,
|
|
||||||
lobby.reactor_handle,
|
|
||||||
lobby.connection_manager,
|
|
||||||
lobby.players,
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
self.state = state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn game_step(&mut self, event: &events::GameStep) {
|
|
||||||
if let PwMatchState::Playing(ref mut controller) = self.state {
|
|
||||||
controller.on_step(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn client_message(&mut self, event: &events::ClientMessage) {
|
|
||||||
if let PwMatchState::Playing(ref mut controller) = self.state {
|
|
||||||
controller.on_client_message(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn game_finished(&mut self, event: &events::GameFinished) {
|
|
||||||
let state = self.take_state();
|
|
||||||
if let PwMatchState::Playing(mut controller) = state {
|
|
||||||
controller.on_finished(event);
|
|
||||||
}
|
|
||||||
self.state = PwMatchState::Finished;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn timeout(&mut self, event: &events::TurnTimeout) {
|
|
||||||
if let PwMatchState::Playing(ref mut controller) = self.state {
|
|
||||||
controller.on_timeout(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Lobby {
|
|
||||||
match_uuid: Vec<u8>,
|
|
||||||
connection_manager: ConnectionManager,
|
|
||||||
reactor_handle: ReactorHandle,
|
|
||||||
|
|
||||||
players: HashMap<ClientId, RegisteredHandle>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Lobby {
|
|
||||||
fn new(match_uuid: Vec<u8>,
|
|
||||||
connection_manager: ConnectionManager,
|
|
||||||
reactor_handle: ReactorHandle)
|
|
||||||
-> Self
|
|
||||||
{
|
|
||||||
return Lobby {
|
|
||||||
match_uuid,
|
|
||||||
connection_manager,
|
|
||||||
reactor_handle,
|
|
||||||
|
|
||||||
players: HashMap::new(),
|
|
||||||
// start counter at 1, because 0 is the control client
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_player(&mut self, client_id: ClientId, public_key: PublicKey) {
|
|
||||||
let mut core = RequestHandler::new(
|
|
||||||
ClientHandler::new(
|
|
||||||
client_id.as_u32(),
|
|
||||||
self.reactor_handle.clone(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
core.add_handler(ClientHandler::on_connect);
|
|
||||||
core.add_handler(ClientHandler::on_disconnect);
|
|
||||||
core.add_handler(ClientHandler::on_message);
|
|
||||||
|
|
||||||
let handle = self.connection_manager.create_client(
|
|
||||||
self.match_uuid.clone(),
|
|
||||||
client_id.as_u32(),
|
|
||||||
public_key,
|
|
||||||
core
|
|
||||||
);
|
|
||||||
|
|
||||||
self.players.insert(client_id, handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_player(&mut self, client_id: ClientId) {
|
|
||||||
self.players.remove(&client_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PwController {
|
|
||||||
state: PlanetWars,
|
|
||||||
planet_map: HashMap<String, usize>,
|
|
||||||
reactor_handle: ReactorHandle,
|
|
||||||
connection_manager: ConnectionManager,
|
|
||||||
|
|
||||||
client_player: HashMap<ClientId, usize>,
|
|
||||||
players: HashMap<ClientId, Player>,
|
|
||||||
|
|
||||||
waiting_for: HashSet<ClientId>,
|
|
||||||
commands: HashMap<ClientId, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PwController {
|
|
||||||
pub fn new(config: Config,
|
|
||||||
reactor_handle: ReactorHandle,
|
|
||||||
connection_manager: ConnectionManager,
|
|
||||||
clients: HashMap<ClientId, RegisteredHandle>)
|
|
||||||
-> Self
|
|
||||||
{
|
|
||||||
// TODO: we probably want a way to fixate player order
|
|
||||||
let client_ids = clients.keys().cloned().collect();
|
|
||||||
let state = config.create_game(client_ids);
|
|
||||||
|
|
||||||
let planet_map = state.planets.iter().map(|planet| {
|
|
||||||
(planet.name.clone(), planet.id)
|
|
||||||
}).collect();
|
|
||||||
|
|
||||||
let mut client_player = HashMap::new();
|
|
||||||
let mut players = HashMap::new();
|
|
||||||
|
|
||||||
let clients_iter = clients.into_iter().enumerate();
|
|
||||||
for (player_num, (client_id, client_handle)) in clients_iter {
|
|
||||||
client_player.insert(client_id, player_num);
|
|
||||||
players.insert(client_id, Player {
|
|
||||||
id: client_id,
|
|
||||||
num: player_num,
|
|
||||||
handle: client_handle,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut controller = PwController {
|
|
||||||
state,
|
|
||||||
planet_map,
|
|
||||||
players,
|
|
||||||
client_player,
|
|
||||||
reactor_handle,
|
|
||||||
connection_manager,
|
|
||||||
|
|
||||||
waiting_for: HashSet::new(),
|
|
||||||
commands: HashMap::new(),
|
|
||||||
};
|
|
||||||
// this initial dispatch starts the game
|
|
||||||
controller.dispatch_state();
|
|
||||||
return controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Advance the game by one turn.
|
|
||||||
fn step(&mut self) {
|
|
||||||
self.state.repopulate();
|
|
||||||
self.execute_commands();
|
|
||||||
self.state.step();
|
|
||||||
|
|
||||||
self.dispatch_state();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dispatch_state(&mut self) {
|
|
||||||
let turn_num = self.state.turn_num;
|
|
||||||
// TODO: fix this
|
|
||||||
let state = serde_json::to_string(&serialize_state(&self.state)).unwrap();
|
|
||||||
|
|
||||||
if self.state.is_finished() {
|
|
||||||
let event = events::GameFinished { turn_num, state };
|
|
||||||
self.reactor_handle.dispatch(event);
|
|
||||||
} else {
|
|
||||||
let event = events::GameStep { turn_num, state };
|
|
||||||
self.reactor_handle.dispatch(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_step(&mut self, step: &events::GameStep) {
|
|
||||||
let state = &self.state;
|
|
||||||
let waiting_for = &mut self.waiting_for;
|
|
||||||
|
|
||||||
self.players.retain(|_, player| {
|
|
||||||
if state.players[player.num].alive {
|
|
||||||
waiting_for.insert(player.id);
|
|
||||||
player.handle.dispatch(events::GameStep {
|
|
||||||
turn_num: step.turn_num,
|
|
||||||
state: step.state.clone(),
|
|
||||||
});
|
|
||||||
// keep this player in the game
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
player.handle.dispatch(events::GameFinished {
|
|
||||||
turn_num: step.turn_num,
|
|
||||||
state: step.state.clone(),
|
|
||||||
});
|
|
||||||
// this player is dead, kick him!
|
|
||||||
// TODO: shutdown the reactor
|
|
||||||
// TODO: todo
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let deadline = Instant::now() + Duration::from_secs(1);
|
|
||||||
self.reactor_handle.dispatch_at(deadline, events::TurnTimeout {
|
|
||||||
turn_num: state.turn_num,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_client_message(&mut self, event: &events::ClientMessage) {
|
|
||||||
let client_id = ClientId::new(event.client_id);
|
|
||||||
self.waiting_for.remove(&client_id);
|
|
||||||
self.commands.insert(client_id, event.data.clone());
|
|
||||||
|
|
||||||
if self.waiting_for.is_empty() {
|
|
||||||
self.step();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_finished(&mut self, event: &events::GameFinished) {
|
|
||||||
self.players.retain(|_player_id, player| {
|
|
||||||
player.handle.dispatch(events::GameFinished {
|
|
||||||
turn_num: event.turn_num,
|
|
||||||
state: event.state.clone(),
|
|
||||||
});
|
|
||||||
// game is over, kick everyone.
|
|
||||||
false
|
|
||||||
});
|
|
||||||
println!("everybody has been kicked");
|
|
||||||
self.reactor_handle.quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_timeout(&mut self, event: &events::TurnTimeout) {
|
|
||||||
if self.state.turn_num == event.turn_num {
|
|
||||||
self.step();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn player_commands(&mut self) -> HashMap<ClientId, Option<String>> {
|
|
||||||
let commands = &mut self.commands;
|
|
||||||
return self.players.values().map(|player| {
|
|
||||||
let command = commands.remove(&player.id);
|
|
||||||
return (player.id, command);
|
|
||||||
}).collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute_commands(&mut self) {
|
|
||||||
let mut commands = self.player_commands();
|
|
||||||
|
|
||||||
for (player_id, command) in commands.drain() {
|
|
||||||
let player_num = self.players[&player_id].num;
|
|
||||||
let action = self.execute_action(player_num, command);
|
|
||||||
let serialized_action = serde_json::to_string(&action).unwrap();
|
|
||||||
self.reactor_handle.dispatch(events::PlayerAction {
|
|
||||||
client_id: player_id.as_u32(),
|
|
||||||
action: serialized_action.clone(),
|
|
||||||
});
|
|
||||||
self.players
|
|
||||||
.get_mut(&player_id)
|
|
||||||
.unwrap()
|
|
||||||
.handle
|
|
||||||
.dispatch(events::PlayerAction {
|
|
||||||
client_id: player_id.as_u32(),
|
|
||||||
action: serialized_action,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute_action(&mut self, player_num: usize, response: Option<String>)
|
|
||||||
-> PlayerAction
|
|
||||||
{
|
|
||||||
// TODO: it would be cool if this could be done with error_chain.
|
|
||||||
|
|
||||||
let message = match response {
|
|
||||||
None => return PlayerAction::Timeout,
|
|
||||||
Some(message) => message,
|
|
||||||
};
|
|
||||||
|
|
||||||
let action: proto::Action = match serde_json::from_str(&message) {
|
|
||||||
Err(err) => return PlayerAction::ParseError(err.to_string()),
|
|
||||||
Ok(action) => action,
|
|
||||||
};
|
|
||||||
|
|
||||||
let commands = action.commands.into_iter().map(|command| {
|
|
||||||
match self.parse_command(player_num, &command) {
|
|
||||||
Ok(dispatch) => {
|
|
||||||
self.state.dispatch(&dispatch);
|
|
||||||
PlayerCommand {
|
|
||||||
command,
|
|
||||||
error: None,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(error) => {
|
|
||||||
PlayerCommand {
|
|
||||||
command,
|
|
||||||
error: Some(error),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).collect();
|
|
||||||
|
|
||||||
return PlayerAction::Commands(commands);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_command(&self, player_num: usize, mv: &proto::Command)
|
|
||||||
-> Result<Dispatch, CommandError>
|
|
||||||
{
|
|
||||||
let origin_id = *self.planet_map
|
|
||||||
.get(&mv.origin)
|
|
||||||
.ok_or(CommandError::OriginDoesNotExist)?;
|
|
||||||
|
|
||||||
let target_id = *self.planet_map
|
|
||||||
.get(&mv.destination)
|
|
||||||
.ok_or(CommandError::DestinationDoesNotExist)?;
|
|
||||||
|
|
||||||
if self.state.planets[origin_id].owner() != Some(player_num) {
|
|
||||||
return Err(CommandError::OriginNotOwned);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.state.planets[origin_id].ship_count() < mv.ship_count {
|
|
||||||
return Err(CommandError::NotEnoughShips);
|
|
||||||
}
|
|
||||||
|
|
||||||
if mv.ship_count == 0 {
|
|
||||||
return Err(CommandError::ZeroShipMove);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Dispatch {
|
|
||||||
origin: origin_id,
|
|
||||||
target: target_id,
|
|
||||||
ship_count: mv.ship_count,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue