fancy frontend for the backend
This commit is contained in:
parent
6f22aea8d1
commit
5b2d873571
10 changed files with 240 additions and 30 deletions
|
@ -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
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
|
@ -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
81
backend/src/routes.rs
Normal 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
27
backend/src/util.rs
Normal 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)
|
||||||
|
}
|
8
backend/static/script/script.js
Normal file
8
backend/static/script/script.js
Normal 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();
|
||||||
|
}
|
3
backend/static/style/style.css
Normal file
3
backend/static/style/style.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.small {
|
||||||
|
font-size: 2px;
|
||||||
|
}
|
16
backend/templates/base.html.tera
Normal file
16
backend/templates/base.html.tera
Normal 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>
|
11
backend/templates/index.html.tera
Normal file
11
backend/templates/index.html.tera
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{% extends "base" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>Hello {{ name }}!</h1>
|
||||||
|
|
||||||
|
{% if maps %}
|
||||||
|
{% include "maps" %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
8
backend/templates/map_partial.html.tera
Normal file
8
backend/templates/map_partial.html.tera
Normal 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>
|
3
backend/templates/maps.html.tera
Normal file
3
backend/templates/maps.html.tera
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{% for m in maps %}
|
||||||
|
<p class="map" onclick="handle_map_click('{{m.url | safe }}')"> {{ m.name }} </p>
|
||||||
|
{% endfor %}
|
Loading…
Reference in a new issue