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;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
@ -12,6 +14,10 @@ extern crate tracing;
|
|||
extern crate tracing_futures;
|
||||
extern crate tracing_subscriber;
|
||||
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
extern crate rocket_contrib;
|
||||
|
||||
use tracing_subscriber::{EnvFilter, FmtSubscriber};
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
@ -27,18 +33,65 @@ use mozaic::graph;
|
|||
use mozaic::modules::*;
|
||||
|
||||
mod planetwars;
|
||||
mod routes;
|
||||
mod util;
|
||||
|
||||
// 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),
|
||||
_ => {}
|
||||
};
|
||||
use rocket_contrib::templates::{Template, Engines};
|
||||
use rocket_contrib::templates::tera::{self, Value};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::cmp::Ordering::Equal;
|
||||
|
||||
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(
|
||||
pool: ThreadPool,
|
||||
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();
|
||||
|
||||
loop {
|
||||
match gm.get_state(current_game).await {
|
||||
None => {
|
||||
println!("Game finished, let's play a new one");
|
||||
let game_builder =
|
||||
build_builder(pool.clone(), number_of_clients, max_turns, map, location);
|
||||
current_game = gm.start_game(game_builder).await.unwrap();
|
||||
}
|
||||
Some(state) => {
|
||||
println!("{:?}", state);
|
||||
}
|
||||
}
|
||||
std::thread::sleep(time::Duration::from_millis(3000));
|
||||
}
|
||||
// loop {
|
||||
// match gm.get_state(current_game).await {
|
||||
// None => {
|
||||
// println!("Game finished, let's play a new one");
|
||||
// let game_builder =
|
||||
// build_builder(pool.clone(), number_of_clients, max_turns, map, location);
|
||||
// current_game = gm.start_game(game_builder).await.unwrap();
|
||||
// }
|
||||
// Some(state) => {
|
||||
// println!("{:?}", state);
|
||||
// }
|
||||
// }
|
||||
// std::thread::sleep(time::Duration::from_millis(3000));
|
||||
// }
|
||||
|
||||
handle.await;
|
||||
|
||||
|
@ -118,9 +171,9 @@ async fn run(args: Vec<String>) -> Option<()> {
|
|||
Some(())
|
||||
}
|
||||
|
||||
fn print_info(name: &str) {
|
||||
println!(
|
||||
"Usage: {} map_location [number_of_clients [output [max_turns]]]",
|
||||
name
|
||||
);
|
||||
}
|
||||
// fn print_info(name: &str) {
|
||||
// println!(
|
||||
// "Usage: {} map_location [number_of_clients [output [max_turns]]]",
|
||||
// name
|
||||
// );
|
||||
// }
|
||||
|
|
|
@ -12,7 +12,7 @@ mod pw_config;
|
|||
mod pw_protocol;
|
||||
mod pw_rules;
|
||||
mod pw_serializer;
|
||||
pub use pw_config::Config;
|
||||
pub use pw_config::{Config, Map};
|
||||
use pw_protocol::{self as proto, CommandError};
|
||||
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