#[macro_use]
extern crate serde_derive;
pub mod analyzer;
pub mod behavior;
pub mod geom;
pub mod models;
use crate::models::{ClientState, GameCommand, GameState, ServerToClient, MIN_COMMAND_INTERVAL};
use failure::Error;
use futures::{Future, Sink, Stream};
use std::{
env,
fmt::Debug,
sync::{Arc, Mutex},
};
use tokio_tungstenite as tokio_ws;
use tokio_ws::tungstenite as ws;
use url::{
percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET},
Url,
};
pub trait Handler {
fn tick(&mut self, state: &ClientState) -> Option<GameCommand>;
}
fn log_err<E: Debug>(e: E) {
eprintln!("{:?}", e)
}
fn is_player_alive(state: &ClientState) -> bool {
state.game_state.players.iter().find(|player| player.id == state.id).is_some()
}
fn build_game_loop<H, S, D>(
sink: S,
client_state: Arc<Mutex<ClientState>>,
mut handler: H,
) -> impl Future<Item = (), Error = ()>
where
H: Handler + Send + 'static,
S: Sink<SinkItem = ws::Message, SinkError = D>,
D: Debug,
{
tokio::timer::Interval::new_interval(MIN_COMMAND_INTERVAL)
.filter_map(move |_| {
let client_state = &*client_state.lock().unwrap();
if is_player_alive(client_state) {
handler.tick(client_state)
} else {
None
}
})
.map(move |command: GameCommand| {
ws::Message::Text(serde_json::to_string(&command).unwrap())
})
.map_err(log_err)
.forward(sink.sink_map_err(log_err))
.map(|_| ()) }
fn build_state_updater<S, D>(
stream: S,
client_state: Arc<Mutex<ClientState>>,
) -> impl Future<Item = (), Error = ()>
where
S: Stream<Item = ws::Message, Error = D>,
D: Debug,
{
stream
.filter_map(|message| message.into_text().ok())
.filter_map(|message| serde_json::from_str(&message).ok())
.for_each(move |server_to_client_msg| {
match server_to_client_msg {
ServerToClient::Id(player_id) => {
(*client_state).lock().unwrap().id = player_id;
},
ServerToClient::GameState(state) => {
(*client_state).lock().unwrap().game_state = state;
},
_ => {},
}
Ok(())
})
.map_err(log_err)
}
pub fn run<H>(key: &str, name: &str, handler: H) -> Result<(), Error>
where
H: Handler + Send + 'static,
{
let host = env::var("SERVER_HOST").unwrap_or("192.168.0.199".into());
let url = Url::parse(&format!(
"ws://{}/socket?key={}&name={}",
host,
key,
utf8_percent_encode(name, DEFAULT_ENCODE_SET).to_string()
))?;
let client_state =
Arc::new(Mutex::new(ClientState { id: 0, game_state: GameState::default() }));
let client = tokio_ws::connect_async(url)
.and_then(move |(websocket, _)| {
let (sink, stream) = websocket.split();
let game_loop = build_game_loop(sink, client_state.clone(), handler);
let state_updater = build_state_updater(stream, client_state);
state_updater.select(game_loop).then(|_| Ok(()))
})
.map_err(log_err);
tokio::run(client);
Ok(())
}
#[cfg(test)]
mod tests {}