Refactor before it is too late
This commit is contained in:
parent
5404f4256a
commit
6b804724b4
13 changed files with 312 additions and 254 deletions
|
@ -33,7 +33,6 @@ use futures::future::FutureExt;
|
||||||
use mozaic::graph;
|
use mozaic::graph;
|
||||||
use mozaic::modules::*;
|
use mozaic::modules::*;
|
||||||
|
|
||||||
mod info;
|
|
||||||
mod planetwars;
|
mod planetwars;
|
||||||
mod routes;
|
mod routes;
|
||||||
mod util;
|
mod util;
|
||||||
|
@ -45,6 +44,8 @@ use rocket_contrib::templates::tera::{self, Value};
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// Calculate viewbox from array of points (used in map preview), added to Tera engine.
|
||||||
|
/// So this function can be called in template.
|
||||||
fn calc_viewbox(value: Value, _: HashMap<String, Value>) -> tera::Result<Value> {
|
fn calc_viewbox(value: Value, _: HashMap<String, Value>) -> tera::Result<Value> {
|
||||||
let mut min_x = std::f64::MAX;
|
let mut min_x = std::f64::MAX;
|
||||||
let mut min_y = std::f64::MAX;
|
let mut min_y = std::f64::MAX;
|
||||||
|
@ -62,10 +63,12 @@ fn calc_viewbox(value: Value, _: HashMap<String, Value>) -> tera::Result<Value>
|
||||||
return Ok(Value::String(format!("{} {} {} {}", min_x - 3., min_y - 3., (max_x - min_x) + 6., (max_y - min_y) + 6.)));
|
return Ok(Value::String(format!("{} {} {} {}", min_x - 3., min_y - 3., (max_x - min_x) + 6., (max_y - min_y) + 6.)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get's the right colour for planets
|
||||||
fn get_colour(value: Value, _: HashMap<String, Value>) -> tera::Result<Value> {
|
fn get_colour(value: Value, _: HashMap<String, Value>) -> tera::Result<Value> {
|
||||||
return Ok(Value::String(COLOURS[value.as_u64().unwrap_or(0) as usize].to_string()));
|
return Ok(Value::String(COLOURS[value.as_u64().unwrap_or(0) as usize].to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Async main function, starting logger, graph and rocket
|
||||||
#[async_std::main]
|
#[async_std::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let fut = graph::set_default();
|
let fut = graph::set_default();
|
||||||
|
@ -81,7 +84,6 @@ async fn main() {
|
||||||
|
|
||||||
let mut routes = Vec::new();
|
let mut routes = Vec::new();
|
||||||
routes::fuel(&mut routes);
|
routes::fuel(&mut routes);
|
||||||
info::fuel(&mut routes);
|
|
||||||
|
|
||||||
let tera = Template::custom(|engines: &mut Engines| {
|
let tera = Template::custom(|engines: &mut Engines| {
|
||||||
engines.tera.register_filter("calc_viewbox", calc_viewbox);
|
engines.tera.register_filter("calc_viewbox", calc_viewbox);
|
||||||
|
@ -98,6 +100,8 @@ async fn main() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates the actual game_manager
|
||||||
|
/// Opening tcp socket etc..
|
||||||
async fn create_game_manager(tcp: &str, pool: ThreadPool) -> game::Manager {
|
async fn create_game_manager(tcp: &str, pool: ThreadPool) -> game::Manager {
|
||||||
let addr = tcp.parse::<SocketAddr>().unwrap();
|
let addr = tcp.parse::<SocketAddr>().unwrap();
|
||||||
let (gmb, handle) = game::Manager::builder(pool.clone());
|
let (gmb, handle) = game::Manager::builder(pool.clone());
|
||||||
|
|
|
@ -7,22 +7,24 @@ use crate::util::*;
|
||||||
|
|
||||||
const MAX: usize = 6;
|
const MAX: usize = 6;
|
||||||
|
|
||||||
|
/// Redirects to the first info page
|
||||||
#[get("/info")]
|
#[get("/info")]
|
||||||
fn help_base() -> Redirect {
|
fn info_base() -> Redirect {
|
||||||
Redirect::to("/info/1")
|
Redirect::to("/info/1")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Renders the <page> info page
|
||||||
#[get("/info/<page>")]
|
#[get("/info/<page>")]
|
||||||
async fn help(page: usize) -> Template {
|
async fn info(page: usize) -> Template {
|
||||||
let context = Context::new_with("info", json!({
|
let context = Context::new_with("info", json!({
|
||||||
"page": page,
|
"page": page,
|
||||||
"next": if page + 1 <= MAX { Some(page + 1) } else { None },
|
"next": if page + 1 <= MAX { Some(page + 1) } else { None },
|
||||||
"prev": if page - 1 > 0 { Some(page - 1) } else { None }
|
"prev": if page - 1 > 0 { Some(page - 1) } else { None }
|
||||||
}));
|
}));
|
||||||
|
|
||||||
Template::render(format!("help/help_{}", page), &context)
|
Template::render(format!("info/info_{}", page), &context)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fuel(routes: &mut Vec<Route>) {
|
pub fn fuel(routes: &mut Vec<Route>) {
|
||||||
routes.extend(routes![help_base, help]);
|
routes.extend(routes![info_base, info]);
|
||||||
}
|
}
|
|
@ -1,102 +1,25 @@
|
||||||
|
use crate::planetwars;
|
||||||
use serde::{Deserialize};
|
|
||||||
use serde_json::Value;
|
|
||||||
|
|
||||||
use rocket::{Route, State};
|
|
||||||
use rocket::response::NamedFile;
|
|
||||||
|
|
||||||
use rocket_contrib::templates::Template;
|
|
||||||
use rocket_contrib::json::Json;
|
|
||||||
|
|
||||||
use async_std::prelude::*;
|
|
||||||
use async_std::fs;
|
|
||||||
use async_std::io::ReadExt;
|
|
||||||
|
|
||||||
use crate::util::*;
|
use crate::util::*;
|
||||||
|
|
||||||
use std::path::Path;
|
use rocket::{State, Route};
|
||||||
|
use rocket_contrib::json::Json;
|
||||||
|
use rocket_contrib::templates::Template;
|
||||||
|
|
||||||
#[get("/<file..>", rank = 6)]
|
use mozaic::modules::types::*;
|
||||||
async fn files(file: PathBuf) -> Option<NamedFile> {
|
use mozaic::modules::{game, StepLock};
|
||||||
NamedFile::open(Path::new("static/").join(file)).ok()
|
use mozaic::util::request::Connect;
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/")]
|
use async_std::fs;
|
||||||
async fn index() -> Template {
|
use async_std::prelude::StreamExt;
|
||||||
// let context = context();
|
|
||||||
let context = Context::new("Home");
|
|
||||||
// context.insert("name".to_string(), "Arthur".to_string());
|
|
||||||
Template::render("index", &context)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
use futures::executor::ThreadPool;
|
||||||
struct MapReq {
|
|
||||||
pub name: String,
|
|
||||||
pub map: crate::planetwars::Map,
|
|
||||||
}
|
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use serde_json::Value;
|
||||||
|
|
||||||
#[post("/maps", data="<map_req>")]
|
|
||||||
async fn map_post(map_req: Json<MapReq>) -> Result<String, String> {
|
|
||||||
let MapReq { name, map } = map_req.into_inner();
|
|
||||||
|
|
||||||
let path: PathBuf = PathBuf::from(format!("maps/{}.json", name));
|
|
||||||
if path.exists() {
|
|
||||||
return Err("File already exists!".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut file = fs::File::create(path).await.map_err(|_| "IO error".to_string())?;
|
|
||||||
file.write_all(&serde_json::to_vec_pretty(&map).unwrap()).await.map_err(|_| "IO error".to_string())?;
|
|
||||||
|
|
||||||
Ok("ok".into())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/lobby")]
|
|
||||||
async fn lobby_get(gm: State<'_, game::Manager>, state: State<'_, Games>) -> Result<Template, String> {
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/mapbuilder")]
|
|
||||||
async fn builder_get() -> Result<Template, String> {
|
|
||||||
let context = Context::new("Map Builder");
|
|
||||||
Ok(Template::render("mapbuilder", &context))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/debug")]
|
|
||||||
async fn debug_get() -> Result<Template, String> {
|
|
||||||
let context = Context::new("Debug Station");
|
|
||||||
Ok(Template::render("debug", &context))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/visualizer")]
|
|
||||||
async fn visualizer_get() -> Template {
|
|
||||||
let game_options = get_games().await;
|
|
||||||
let context = Context::new_with("Visualizer", json!({"games": game_options, "colours": COLOURS}));
|
|
||||||
Template::render("visualizer", &context)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/maps/<file>")]
|
|
||||||
async fn map_get(file: String) -> Result<Template, String> {
|
|
||||||
let mut content = String::new();
|
|
||||||
let mut file = fs::File::open(Path::new("maps/").join(file)).await.map_err(|_| "IO ERROR".to_string())?;
|
|
||||||
file.read_to_string(&mut content).await.map_err(|_| "IO ERROR".to_string())?;
|
|
||||||
|
|
||||||
Ok(Template::render("map_partial", &serde_json::from_str::<serde_json::Value>(&content).unwrap()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/partial/state")]
|
|
||||||
async fn state_get(gm: State<'_, game::Manager>, state: State<'_, Games>) -> Result<Template, String> {
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
use rand::prelude::*;
|
||||||
|
|
||||||
|
/// The type required to build a game.
|
||||||
|
/// (json in POST request).
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
struct GameReq {
|
struct GameReq {
|
||||||
nop: u64,
|
nop: u64,
|
||||||
|
@ -105,15 +28,35 @@ struct GameReq {
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Response when building a game.
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct GameRes {
|
struct GameRes {
|
||||||
players: Vec<u64>,
|
players: Vec<u64>,
|
||||||
state: Value,
|
state: Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
use mozaic::util::request::Connect;
|
/// Standard get function for the lobby tab
|
||||||
|
#[get("/lobby")]
|
||||||
|
async fn get_lobby(gm: State<'_, game::Manager>, state: State<'_, Games>) -> Result<Template, String> {
|
||||||
|
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<Template, String> {
|
||||||
|
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="<game_req>")]
|
#[post("/lobby", data="<game_req>")]
|
||||||
async fn game_post(game_req: Json<GameReq>, tp: State<'_, ThreadPool>, gm: State<'_, game::Manager>, state: State<'_, Games>) -> Result<Json<GameRes>, String> {
|
async fn post_game(game_req: Json<GameReq>, tp: State<'_, ThreadPool>, gm: State<'_, game::Manager>, state: State<'_, Games>) -> Result<Json<GameRes>, String> {
|
||||||
let game = build_builder(tp.clone(), game_req.nop, game_req.max_turns, &game_req.map, &game_req.name);
|
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();
|
let game_id = gm.start_game(game).await.unwrap();
|
||||||
state.add_game(game_req.name.clone(), game_id);
|
state.add_game(game_req.name.clone(), game_id);
|
||||||
|
@ -136,17 +79,7 @@ async fn game_post(game_req: Json<GameReq>, tp: State<'_, ThreadPool>, gm: State
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fuel(routes: &mut Vec<Route>) {
|
/// Generate random ID for the game, used as filename
|
||||||
routes.extend(routes![files, index, map_post, map_get, lobby_get, builder_get, visualizer_get, game_post, state_get, debug_get]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
use crate::planetwars;
|
|
||||||
use mozaic::modules::types::*;
|
|
||||||
use mozaic::modules::{game, StepLock};
|
|
||||||
use futures::executor::ThreadPool;
|
|
||||||
|
|
||||||
use rand::prelude::*;
|
|
||||||
fn generate_string_id() -> String {
|
fn generate_string_id() -> String {
|
||||||
rand::thread_rng()
|
rand::thread_rng()
|
||||||
.sample_iter(&rand::distributions::Alphanumeric)
|
.sample_iter(&rand::distributions::Alphanumeric)
|
||||||
|
@ -154,6 +87,8 @@ fn generate_string_id() -> String {
|
||||||
.collect::<String>() + ".json"
|
.collect::<String>() + ".json"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// game::Manager spawns game::Builder to start games.
|
||||||
|
/// This returns such a Builder for a planetwars game.
|
||||||
fn build_builder(
|
fn build_builder(
|
||||||
pool: ThreadPool,
|
pool: ThreadPool,
|
||||||
number_of_clients: u64,
|
number_of_clients: u64,
|
||||||
|
@ -176,3 +111,83 @@ fn build_builder(
|
||||||
.with_timeout(std::time::Duration::from_secs(1)),
|
.with_timeout(std::time::Duration::from_secs(1)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fuels the lobby routes
|
||||||
|
pub fn fuel(routes: &mut Vec<Route>) {
|
||||||
|
routes.extend(routes![post_game, get_lobby, state_get]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct Lobby {
|
||||||
|
pub games: Vec<GameState>,
|
||||||
|
pub maps: Vec<Map>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct Map {
|
||||||
|
name: String,
|
||||||
|
url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_maps() -> Result<Vec<Map>, 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<Vec<GameState>, 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<PlayerStatus> =
|
||||||
|
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)
|
||||||
|
}
|
50
backend/src/routes/maps.rs
Normal file
50
backend/src/routes/maps.rs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
use serde::{Deserialize};
|
||||||
|
|
||||||
|
use rocket::{Route};
|
||||||
|
use rocket_contrib::templates::Template;
|
||||||
|
use rocket_contrib::json::Json;
|
||||||
|
|
||||||
|
use async_std::prelude::*;
|
||||||
|
use async_std::fs;
|
||||||
|
use async_std::io::ReadExt;
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
/// The expected body to create a map.
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct MapReq {
|
||||||
|
pub name: String,
|
||||||
|
pub map: crate::planetwars::Map,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Post route to create a map.
|
||||||
|
#[post("/maps", data="<map_req>")]
|
||||||
|
async fn map_post(map_req: Json<MapReq>) -> Result<String, String> {
|
||||||
|
let MapReq { name, map } = map_req.into_inner();
|
||||||
|
|
||||||
|
let path: PathBuf = PathBuf::from(format!("maps/{}.json", name));
|
||||||
|
if path.exists() {
|
||||||
|
return Err("File already exists!".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file = fs::File::create(path).await.map_err(|_| "IO error".to_string())?;
|
||||||
|
file.write_all(&serde_json::to_vec_pretty(&map).unwrap()).await.map_err(|_| "IO error".to_string())?;
|
||||||
|
|
||||||
|
Ok("ok".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Map partial, rendering a map as svg and returning the svg element
|
||||||
|
/// Used in the lobby page for the map previewer
|
||||||
|
#[get("/maps/<file>")]
|
||||||
|
async fn map_get(file: String) -> Result<Template, String> {
|
||||||
|
let mut content = String::new();
|
||||||
|
let mut file = fs::File::open(Path::new("maps/").join(file)).await.map_err(|_| "IO ERROR".to_string())?;
|
||||||
|
file.read_to_string(&mut content).await.map_err(|_| "IO ERROR".to_string())?;
|
||||||
|
|
||||||
|
Ok(Template::render("map_partial", &serde_json::from_str::<serde_json::Value>(&content).unwrap()))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn fuel(routes: &mut Vec<Route>) {
|
||||||
|
routes.extend(routes![map_post, map_get]);
|
||||||
|
}
|
82
backend/src/routes/mod.rs
Normal file
82
backend/src/routes/mod.rs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
use crate::util::*;
|
||||||
|
use crate::planetwars::FinishedState;
|
||||||
|
|
||||||
|
use rocket::{Route};
|
||||||
|
use rocket::response::NamedFile;
|
||||||
|
use rocket_contrib::templates::Template;
|
||||||
|
|
||||||
|
use async_std::prelude::*;
|
||||||
|
use async_std::io::BufReader;
|
||||||
|
use async_std::fs;
|
||||||
|
|
||||||
|
use futures::stream::StreamExt;
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
mod lobby;
|
||||||
|
mod maps;
|
||||||
|
mod info;
|
||||||
|
|
||||||
|
/// Handles all files located in the static folder
|
||||||
|
#[get("/<file..>", rank = 6)]
|
||||||
|
async fn files(file: PathBuf) -> Option<NamedFile> {
|
||||||
|
NamedFile::open(Path::new("static/").join(file)).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Routes the index page, rendering the index Template.
|
||||||
|
#[get("/")]
|
||||||
|
async fn index() -> Template {
|
||||||
|
let context = Context::new("Home");
|
||||||
|
Template::render("index", &context)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Routes the mapbuilder page, rendering the mapbuilder Template.
|
||||||
|
#[get("/mapbuilder")]
|
||||||
|
async fn builder_get() -> Result<Template, String> {
|
||||||
|
let context = Context::new("Map Builder");
|
||||||
|
Ok(Template::render("mapbuilder", &context))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Routes the debug page, rendering the debug Template.
|
||||||
|
#[get("/debug")]
|
||||||
|
async fn debug_get() -> Result<Template, String> {
|
||||||
|
let context = Context::new("Debug Station");
|
||||||
|
Ok(Template::render("debug", &context))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Routes the visualizer page, rendering the visualizer Template.
|
||||||
|
#[get("/visualizer")]
|
||||||
|
async fn visualizer_get() -> Template {
|
||||||
|
let game_options = get_played_games().await;
|
||||||
|
let context = Context::new_with("Visualizer", json!({"games": game_options, "colours": COLOURS}));
|
||||||
|
Template::render("visualizer", &context)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fuels all routes
|
||||||
|
pub fn fuel(routes: &mut Vec<Route>) {
|
||||||
|
routes.extend(routes![files, index, builder_get, visualizer_get, debug_get]);
|
||||||
|
lobby::fuel(routes);
|
||||||
|
maps::fuel(routes);
|
||||||
|
info::fuel(routes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads games.ini
|
||||||
|
/// File that represents all played games
|
||||||
|
/// Ready to be visualized
|
||||||
|
async fn get_played_games() -> Vec<GameState> {
|
||||||
|
match fs::File::open("games.ini").await {
|
||||||
|
Ok(file) => {
|
||||||
|
let file = BufReader::new(file);
|
||||||
|
file.lines()
|
||||||
|
.filter_map(move |maybe| async {
|
||||||
|
maybe
|
||||||
|
.ok()
|
||||||
|
.and_then(|line| serde_json::from_str::<FinishedState>(&line).ok())
|
||||||
|
})
|
||||||
|
.map(|state| state.into())
|
||||||
|
.collect()
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
Err(_) => Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
use async_std::fs;
|
use crate::planetwars::FinishedState;
|
||||||
use async_std::prelude::*;
|
use mozaic::util::request::Connect;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
static NAV: [(&'static str, &'static str); 6] = [
|
static NAV: [(&'static str, &'static str); 6] = [
|
||||||
("/", "Home"),
|
("/", "Home"),
|
||||||
|
@ -14,18 +18,14 @@ pub static COLOURS: [&'static str; 9] = [
|
||||||
"gray", "blue", "cyan", "green", "yellow", "orange", "red", "pink", "purple",
|
"gray", "blue", "cyan", "green", "yellow", "orange", "red", "pink", "purple",
|
||||||
];
|
];
|
||||||
|
|
||||||
#[derive(Serialize)]
|
/// The state of a player, in a running game.
|
||||||
pub struct Map {
|
/// This represents actual players or connection keys.
|
||||||
name: String,
|
|
||||||
url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Eq, PartialEq)]
|
#[derive(Serialize, Eq, PartialEq)]
|
||||||
pub struct PlayerStatus {
|
pub struct PlayerStatus {
|
||||||
waiting: bool,
|
pub waiting: bool,
|
||||||
connected: bool,
|
pub connected: bool,
|
||||||
reconnecting: bool,
|
pub reconnecting: bool,
|
||||||
value: String,
|
pub value: String,
|
||||||
}
|
}
|
||||||
impl From<Connect> for PlayerStatus {
|
impl From<Connect> for PlayerStatus {
|
||||||
fn from(value: Connect) -> Self {
|
fn from(value: Connect) -> Self {
|
||||||
|
@ -53,8 +53,9 @@ impl From<Connect> for PlayerStatus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use serde_json::Value;
|
/// The GameState is the state of a game.
|
||||||
|
/// Either Finished, so the game is done, not running, and there is a posible visualization.
|
||||||
|
/// Or Playing, the game is still being managed by the mozaic framework.
|
||||||
#[derive(Serialize, Eq, PartialEq)]
|
#[derive(Serialize, Eq, PartialEq)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum GameState {
|
pub enum GameState {
|
||||||
|
@ -75,8 +76,6 @@ pub enum GameState {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::cmp::Ordering;
|
|
||||||
|
|
||||||
impl PartialOrd for GameState {
|
impl PartialOrd for GameState {
|
||||||
fn partial_cmp(&self, other: &GameState) -> Option<Ordering> {
|
fn partial_cmp(&self, other: &GameState) -> Option<Ordering> {
|
||||||
Some(self.cmp(other))
|
Some(self.cmp(other))
|
||||||
|
@ -116,6 +115,7 @@ impl From<FinishedState> for GameState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Link struct, holding all necessary information
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct Link {
|
struct Link {
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -123,18 +123,21 @@ struct Link {
|
||||||
active: bool,
|
active: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
impl Link {
|
||||||
pub struct Lobby {
|
fn build_nav(active: &str) -> Vec<Link> {
|
||||||
pub games: Vec<GameState>,
|
NAV.iter()
|
||||||
pub maps: Vec<Map>,
|
.map(|(href, name)| Link {
|
||||||
|
name: name.to_string(),
|
||||||
|
href: href.to_string(),
|
||||||
|
active: *name == active,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[derive(Serialize)]
|
/// Context used as template context.
|
||||||
// #[serde(rename_all = "camelCase")]
|
/// This way you know to add nav bar support etc.
|
||||||
// pub enum ContextT {
|
/// This T can be anything that is serializable, like json!({}) macro.
|
||||||
// Games(Vec<GameState>),
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct Context<T> {
|
pub struct Context<T> {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -146,14 +149,7 @@ pub struct Context<T> {
|
||||||
|
|
||||||
impl<T> Context<T> {
|
impl<T> Context<T> {
|
||||||
pub fn new_with(active: &str, t: T) -> Self {
|
pub fn new_with(active: &str, t: T) -> Self {
|
||||||
let nav = NAV
|
let nav = Link::build_nav(active);
|
||||||
.iter()
|
|
||||||
.map(|(href, name)| Link {
|
|
||||||
name: name.to_string(),
|
|
||||||
href: href.to_string(),
|
|
||||||
active: *name == active,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Context {
|
Context {
|
||||||
nav,
|
nav,
|
||||||
|
@ -165,14 +161,7 @@ impl<T> Context<T> {
|
||||||
|
|
||||||
impl Context<()> {
|
impl Context<()> {
|
||||||
pub fn new(active: &str) -> Self {
|
pub fn new(active: &str) -> Self {
|
||||||
let nav = NAV
|
let nav = Link::build_nav(active);
|
||||||
.iter()
|
|
||||||
.map(|(href, name)| Link {
|
|
||||||
name: name.to_string(),
|
|
||||||
href: href.to_string(),
|
|
||||||
active: *name == active,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Context {
|
Context {
|
||||||
nav,
|
nav,
|
||||||
|
@ -182,92 +171,8 @@ impl Context<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_maps() -> Result<Vec<Map>, 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 async_std::io::BufReader;
|
|
||||||
use futures::stream::StreamExt;
|
|
||||||
pub async fn get_games() -> Vec<GameState> {
|
|
||||||
match fs::File::open("games.ini").await {
|
|
||||||
Ok(file) => {
|
|
||||||
let file = BufReader::new(file);
|
|
||||||
file.lines()
|
|
||||||
.filter_map(move |maybe| async {
|
|
||||||
maybe
|
|
||||||
.ok()
|
|
||||||
.and_then(|line| serde_json::from_str::<FinishedState>(&line).ok())
|
|
||||||
})
|
|
||||||
.map(|state| state.into())
|
|
||||||
.collect()
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
Err(_) => Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use crate::planetwars::FinishedState;
|
|
||||||
|
|
||||||
use futures::future::{join_all, FutureExt};
|
|
||||||
use mozaic::modules::game;
|
|
||||||
use mozaic::util::request::Connect;
|
|
||||||
pub async fn get_states(
|
|
||||||
game_ids: &Vec<(String, u64)>,
|
|
||||||
manager: &game::Manager,
|
|
||||||
) -> Result<Vec<GameState>, 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<PlayerStatus> =
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
|
/// Games is the game manager wrapper so Rocket can manage it
|
||||||
pub struct Games {
|
pub struct Games {
|
||||||
inner: Arc<Mutex<Vec<(String, u64)>>>,
|
inner: Arc<Mutex<Vec<(String, u64)>>>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
</div>
|
</div>
|
||||||
<hr style="width:100%">
|
<hr style="width:100%">
|
||||||
<div class="help_content">
|
<div class="help_content">
|
||||||
{% block help %}{% endblock help %}
|
{% block info %}{% endblock info %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "help/base" %}
|
{% extends "info/base" %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ Information
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block help %}
|
{% block info %}
|
||||||
|
|
||||||
<div class="help_content_2">
|
<div class="help_content_2">
|
||||||
<h2>Rules</h2>
|
<h2>Rules</h2>
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "help/base" %}
|
{% extends "info/base" %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ Information
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block help %}
|
{% block info %}
|
||||||
|
|
||||||
<div class="help_content_2">
|
<div class="help_content_2">
|
||||||
<h2>Combat resolution</h2>
|
<h2>Combat resolution</h2>
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "help/base" %}
|
{% extends "info/base" %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ Interaction with the game
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block help %}
|
{% block info %}
|
||||||
|
|
||||||
<div class="help_content_1">
|
<div class="help_content_1">
|
||||||
<div class="boxed centering">
|
<div class="boxed centering">
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "help/base" %}
|
{% extends "info/base" %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ Format: Game state
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block help %}
|
{% block info %}
|
||||||
|
|
||||||
<div class="help_content_2">
|
<div class="help_content_2">
|
||||||
<pre>
|
<pre>
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "help/base" %}
|
{% extends "info/base" %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ Format: Player turn (actions)
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block help %}
|
{% block info %}
|
||||||
|
|
||||||
<div class="help_content_2">
|
<div class="help_content_2">
|
||||||
<pre>
|
<pre>
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "help/base" %}
|
{% extends "info/base" %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ How to connect
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block help %}
|
{% block info %}
|
||||||
|
|
||||||
<div class="help_content_2">
|
<div class="help_content_2">
|
||||||
|
|
Loading…
Reference in a new issue