fancy frontend for the backend

This commit is contained in:
ajuvercr 2020-03-25 22:14:26 +01:00
parent 6f22aea8d1
commit 5b2d873571
10 changed files with 240 additions and 30 deletions

View file

@ -1,3 +1,5 @@
#![feature(proc_macro_hygiene)]
extern crate serde; extern crate serde;
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
@ -12,6 +14,10 @@ extern crate tracing;
extern crate tracing_futures; extern crate tracing_futures;
extern crate tracing_subscriber; extern crate tracing_subscriber;
#[macro_use]
extern crate rocket;
extern crate rocket_contrib;
use tracing_subscriber::{EnvFilter, FmtSubscriber}; use tracing_subscriber::{EnvFilter, FmtSubscriber};
use std::net::SocketAddr; use std::net::SocketAddr;
@ -27,18 +33,65 @@ use mozaic::graph;
use mozaic::modules::*; use mozaic::modules::*;
mod planetwars; mod planetwars;
mod routes;
mod util;
// Load the config and start the game. use rocket_contrib::templates::{Template, Engines};
#[async_std::main] use rocket_contrib::templates::tera::{self, Value};
async fn main() {
let args: Vec<String> = env::args().collect(); use std::collections::HashMap;
let name = args[0].clone(); use std::cmp::Ordering::Equal;
match run(args).await {
None => print_info(&name), const COLOURS: [&'static str; 9] = ["grey", "blue", "cyan", "green", "yellow", "orange", "red", "pink", "purple"];
_ => {}
}; fn calc_viewbox(value: Value, _: HashMap<String, Value>) -> tera::Result<Value> {
let mut min_x = std::f64::MAX;
let mut min_y = std::f64::MAX;
let mut max_x = std::f64::MIN;
let mut max_y = std::f64::MIN;
for v in value.as_array().unwrap() {
let x = v.get("x").and_then(|v| v.as_f64()).unwrap();
let y = v.get("y").and_then(|v| v.as_f64()).unwrap();
if x < min_x { min_x = x; }
if x > max_x { max_x = x; }
if y < min_y { min_y = y; }
if y > max_y { max_y = y; }
}
return Ok(Value::String(format!("{} {} {} {}", min_x - 3., min_y - 3., (max_x - min_x) + 6., (max_y - min_y) + 6.)));
} }
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()));
}
fn main() {
let mut routes = Vec::new();
routes::fuel(&mut routes);
let tera = Template::custom(|engines: &mut Engines| {
engines.tera.register_filter("calc_viewbox", calc_viewbox);
engines.tera.register_filter("get_colour", get_colour);
});
rocket::ignite()
.attach(tera)
.mount("/", routes)
.launch()
.unwrap();
}
// // Load the config and start the game.
// #[async_std::main]
// async fn main() {
// let args: Vec<String> = env::args().collect();
// let name = args[0].clone();
// match run(args).await {
// None => print_info(&name),
// _ => {}
// };
// }
fn build_builder( fn build_builder(
pool: ThreadPool, pool: ThreadPool,
number_of_clients: u64, number_of_clients: u64,
@ -96,20 +149,20 @@ async fn run(args: Vec<String>) -> Option<()> {
let mut current_game = gm.start_game(game_builder).await.unwrap(); let mut current_game = gm.start_game(game_builder).await.unwrap();
loop { // loop {
match gm.get_state(current_game).await { // match gm.get_state(current_game).await {
None => { // None => {
println!("Game finished, let's play a new one"); // println!("Game finished, let's play a new one");
let game_builder = // let game_builder =
build_builder(pool.clone(), number_of_clients, max_turns, map, location); // build_builder(pool.clone(), number_of_clients, max_turns, map, location);
current_game = gm.start_game(game_builder).await.unwrap(); // current_game = gm.start_game(game_builder).await.unwrap();
} // }
Some(state) => { // Some(state) => {
println!("{:?}", state); // println!("{:?}", state);
} // }
} // }
std::thread::sleep(time::Duration::from_millis(3000)); // std::thread::sleep(time::Duration::from_millis(3000));
} // }
handle.await; handle.await;
@ -118,9 +171,9 @@ async fn run(args: Vec<String>) -> Option<()> {
Some(()) Some(())
} }
fn print_info(name: &str) { // fn print_info(name: &str) {
println!( // println!(
"Usage: {} map_location [number_of_clients [output [max_turns]]]", // "Usage: {} map_location [number_of_clients [output [max_turns]]]",
name // name
); // );
} // }

View file

@ -12,7 +12,7 @@ mod pw_config;
mod pw_protocol; mod pw_protocol;
mod pw_rules; mod pw_rules;
mod pw_serializer; mod pw_serializer;
pub use pw_config::Config; pub use pw_config::{Config, Map};
use pw_protocol::{self as proto, CommandError}; use pw_protocol::{self as proto, CommandError};
use pw_rules::Dispatch; use pw_rules::Dispatch;

81
backend/src/routes.rs Normal file
View file

@ -0,0 +1,81 @@
use serde::{Deserialize};
use rocket::Route;
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 std::path::Path;
#[get("/<file..>", rank = 6)]
async fn files(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(Path::new("static/").join(file)).ok()
}
#[get("/")]
async fn index() -> Template {
// let context = context();
let context = Context { name: "Arthur".into(), maps: None };
// context.insert("name".to_string(), "Arthur".to_string());
Template::render("index", &context)
}
#[get("/status")]
async fn status() -> Template {
// let context = context();
let context = Context { name: "Arthur".into(), maps: None };
// context.insert("name".to_string(), "Arthur".to_string());
Template::render("index", &context)
}
#[derive(Deserialize, Debug)]
struct MapReq {
pub name: String,
pub map: crate::planetwars::Map,
}
use std::path::PathBuf;
#[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("/maps")]
async fn maps_get() -> Result<Template, String> {
let maps = get_maps().await?;
let context = Context { name: "Arthur".into(), maps: Some(maps) };
Ok(Template::render("index", &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()))
}
pub fn fuel(routes: &mut Vec<Route>) {
routes.extend(routes![files, status, index, map_post, map_get, maps_get]);
}

27
backend/src/util.rs Normal file
View file

@ -0,0 +1,27 @@
use async_std::prelude::*;
use async_std::fs;
#[derive(Serialize)]
pub struct Map {
name: String,
url: String,
}
#[derive(Serialize)]
pub struct Context {
pub name: String,
pub maps: Option<Vec<Map>>,
}
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)
}

View file

@ -0,0 +1,8 @@
const ids = {};
["map_holder"].forEach(id => ids[id] = document.getElementById(id));
async function handle_map_click(url) {
console.log(url);
const c = await fetch(url);
ids["map_holder"].innerHTML = await c.text();
}

View file

@ -0,0 +1,3 @@
.small {
font-size: 2px;
}

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Tera Demo</title>
<link rel="stylesheet" href="style/style.css">
</head>
<body>
{% block content %}{% endblock content %}
<div style="width: 50%" id="map_holder">
</div>
<script src="script/script.js"></script>
</body>
</html>

View file

@ -0,0 +1,11 @@
{% extends "base" %}
{% block content %}
<h1>Hello {{ name }}!</h1>
{% if maps %}
{% include "maps" %}
{% endif %}
{% endblock %}

View file

@ -0,0 +1,8 @@
<p> Map rendering </p>
<svg preserveAspectRatio="none" viewBox="{{ planets | calc_viewbox}}" xmlns="http://www.w3.org/2000/svg" fill="grey">
{% for planet in planets %}
<circle cx="{{ planet.x }}" cy="{{ planet.y }}" r="0.5" fill="{% if planet.owner %}{{planet.owner | get_colour}}{% else %}grey{%endif%}"/>
<text x="{{planet.x}}" y="{{planet.y + 2}}" class="small" dominant-baseline="middle" text-anchor="middle">{{planet.name}}</text>
{% endfor %}
</svg>

View file

@ -0,0 +1,3 @@
{% for m in maps %}
<p class="map" onclick="handle_map_click('{{m.url | safe }}')"> {{ m.name }} </p>
{% endfor %}