use crate::planetwars; use crate::util::*; use rocket::{State, Route}; use rocket_contrib::json::Json; use rocket_contrib::templates::Template; use mozaic::modules::types::*; use mozaic::modules::{game, StepLock}; use mozaic::util::request::Connect; use async_std::fs; use async_std::prelude::StreamExt; use futures::executor::ThreadPool; use serde_json::Value; use rand::prelude::*; /// The type required to build a game. /// (json in POST request). #[derive(Deserialize, Debug)] struct GameReq { nop: u64, max_turns: u64, map: String, name: String, } /// Response when building a game. #[derive(Serialize)] struct GameRes { players: Vec, state: Value, } /// Standard get function for the lobby tab #[get("/lobby")] async fn get_lobby(gm: State<'_, game::Manager>, state: State<'_, Games>) -> Result { let maps = get_maps().await?; let games = get_states(&state.get_games(), &gm).await?; let context = Context::new_with("Lobby", Lobby { games, maps }); Ok(Template::render("lobby", &context)) } /// The lobby get's this automatically on load and on refresh. #[get("/partial/state")] async fn state_get(gm: State<'_, game::Manager>, state: State<'_, Games>) -> Result { let games = get_states(&state.get_games(), &gm).await?; let context = Context::new_with("Lobby", Lobby { games, maps: Vec::new() }); Ok(Template::render("state_partial", &context)) } /// Post function to create a game. /// Returns the keys of the players in json. #[post("/lobby", data="")] async fn post_game(game_req: Json, tp: State<'_, ThreadPool>, gm: State<'_, game::Manager>, state: State<'_, Games>) -> Result, String> { let game = build_builder(tp.clone(), game_req.nop, game_req.max_turns, &game_req.map, &game_req.name); let game_id = gm.start_game(game).await.unwrap(); state.add_game(game_req.name.clone(), game_id); match gm.get_state(game_id).await { Some(Ok((state, conns))) => { let players: Vec = conns.iter().map(|conn| match conn { Connect::Waiting(_, key) => *key, _ => 0, }).collect(); Ok(Json(GameRes { players, state })) }, Some(Err(v)) => { Err(serde_json::to_string(&v).unwrap()) }, None => { Err(String::from("Fuck the world")) } } } /// Generate random ID for the game, used as filename fn generate_string_id() -> String { rand::thread_rng() .sample_iter(&rand::distributions::Alphanumeric) .take(15) .collect::() + ".json" } /// game::Manager spawns game::Builder to start games. /// This returns such a Builder for a planetwars game. fn build_builder( pool: ThreadPool, number_of_clients: u64, max_turns: u64, map: &str, name: &str, ) -> game::Builder { let config = planetwars::Config { map_file: map.to_string(), max_turns: max_turns, }; let game = planetwars::PlanetWarsGame::new(config.create_game(number_of_clients as usize), &generate_string_id(), name, map); let players: Vec = (0..number_of_clients).collect(); game::Builder::new(players.clone(), game).with_step_lock( StepLock::new(players.clone(), pool.clone()) .with_timeout(std::time::Duration::from_secs(1)), ) } /// Fuels the lobby routes pub fn fuel(routes: &mut Vec) { routes.extend(routes![post_game, get_lobby, state_get]); } #[derive(Serialize)] pub struct Lobby { pub games: Vec, pub maps: Vec, } #[derive(Serialize)] pub struct Map { name: String, url: String, } async fn get_maps() -> Result, String> { let mut maps = Vec::new(); let mut entries = fs::read_dir("maps") .await .map_err(|_| "IO error".to_string())?; while let Some(file) = entries.next().await { let file = file.map_err(|_| "IO error".to_string())?.path(); if let Some(stem) = file.file_stem().and_then(|x| x.to_str()) { maps.push(Map { name: stem.to_string(), url: file.to_str().unwrap().to_string(), }); } } Ok(maps) } use crate::planetwars::FinishedState; use futures::future::{join_all, FutureExt}; pub async fn get_states( game_ids: &Vec<(String, u64)>, manager: &game::Manager, ) -> Result, String> { let mut states = Vec::new(); let gss = join_all( game_ids .iter() .cloned() .map(|(name, id)| manager.get_state(id).map(move |f| (f, name))), ) .await; for (gs, name) in gss { if let Some(state) = gs { match state { Ok((state, conns)) => { let players: Vec = conns.iter().cloned().map(|x| x.into()).collect(); let connected = players.iter().filter(|x| x.connected).count(); states.push(GameState::Playing { name: name, total: players.len(), players, connected, map: String::new(), state, }); } Err(value) => { let state: FinishedState = serde_json::from_value(value).expect("Shit failed"); states.push(state.into()); } } } } states.sort(); Ok(states) }