aboutsummaryrefslogtreecommitdiff
path: root/web/planetwars-rs/src
diff options
context:
space:
mode:
Diffstat (limited to 'web/planetwars-rs/src')
-rw-r--r--web/planetwars-rs/src/lib.rs373
-rw-r--r--web/planetwars-rs/src/types.rs45
-rw-r--r--web/planetwars-rs/src/utils.rs65
3 files changed, 483 insertions, 0 deletions
diff --git a/web/planetwars-rs/src/lib.rs b/web/planetwars-rs/src/lib.rs
new file mode 100644
index 0000000..f2ba7e1
--- /dev/null
+++ b/web/planetwars-rs/src/lib.rs
@@ -0,0 +1,373 @@
+extern crate serde;
+#[macro_use]
+extern crate serde_derive;
+extern crate octoon_math;
+extern crate serde_json;
+extern crate voronoi;
+
+use octoon_math::Mat3;
+use voronoi::{make_polygons, voronoi, Point};
+
+mod types;
+mod utils;
+
+use std::collections::HashMap;
+use wasm_bindgen::prelude::*;
+
+macro_rules! console_log {
+ // Note that this is using the `log` function imported above during
+ // `bare_bones`
+ ($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
+}
+
+// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
+// allocator.
+#[cfg(feature = "wee_alloc")]
+#[global_allocator]
+static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
+
+#[derive(Debug, Clone)]
+pub struct Circle {
+ r: f32,
+ x: f32,
+ y: f32,
+ a0: f32,
+ ad: f32,
+ distance: usize,
+}
+
+use std::f32::consts::PI;
+fn spr(from: f32) -> f32 {
+ let pi2 = PI * 2.;
+ ((from % pi2) + pi2) % pi2
+}
+
+impl Circle {
+ pub fn new(p1: &types::Planet, p2: &types::Planet) -> Self {
+ let x1 = p1.x;
+ let y1 = p1.y;
+ let x2 = p2.x;
+ let y2 = p2.y;
+
+ // Distance between planets
+ let q = ((x2 - x1).powi(2) + (y2 - y1).powi(2)).sqrt();
+ // Center of between planets
+ let x3 = (x1 + x2) / 2.0;
+ let y3 = (y1 + y2) / 2.0;
+
+ // Radius of circle
+ let r = q * 1.0;
+
+ // Center of circle
+ let x = x3 + (r.powi(2) - (q / 2.0).powi(2)).sqrt() * (y1 - y2) / q;
+ let y = y3 + (r.powi(2) - (q / 2.0).powi(2)).sqrt() * (x2 - x1) / q;
+ // console_log!("{},{} -> {},{} ({},{} r={})", x1, y1, x2, y2, x, y, r);
+
+ let a0 = spr((y - y1).atan2(x - x1));
+ let a2 = spr((y - y2).atan2(x - x2));
+
+ let mut ad = spr(a0 - a2);
+ if ad > PI {
+ ad = spr(a2 - a0);
+ }
+ // console_log!("a1 {} a2 {} ad {}", a0/PI * 180.0, a2/PI * 180.0, ad/PI*180.0);
+
+ let distance = q.ceil() as usize + 1;
+ Self {
+ r,
+ x,
+ y,
+ a0,
+ ad,
+ distance,
+ }
+ }
+
+ pub fn get_for_remaining(&self, remaining: usize) -> ((Mat3<f32>, f32), (Mat3<f32>, f32)) {
+ (
+ self.get_remaining(remaining),
+ self.get_remaining((remaining + 1).min(self.distance - 1)),
+ )
+ }
+
+ fn get_remaining(&self, remaining: usize) -> (Mat3<f32>, f32) {
+ let alpha = self.a0 + (1.0 - (remaining as f32 / self.distance as f32)) * self.ad;
+
+ let cos = alpha.cos();
+ let sin = alpha.sin();
+ (
+ Mat3::new(
+ 0.3,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.3,
+ 0.0,
+ -self.x + cos * self.r,
+ -self.y + sin * self.r,
+ 0.3,
+ ),
+ alpha,
+ )
+ }
+}
+
+fn create_voronoi(planets: &Vec<types::Planet>, bbox: f32) -> (Vec<f32>, Vec<usize>) {
+ let mut verts: Vec<[f32; 2]> = planets.iter().map(|p| [p.x, p.y]).collect();
+ let mut ids = Vec::new();
+
+ let vor_points = planets
+ .iter()
+ .map(|p| Point::new(p.x as f64, p.y as f64))
+ .collect();
+
+ let vor = voronoi(vor_points, bbox as f64);
+ let vor = make_polygons(&vor);
+
+ for poly in vor.iter() {
+ // Get planet index for planet that is inside this poligon
+ let idx = 0;
+
+ let mut prev = ids.len() + poly.len() - 1;
+ for p in poly.iter() {
+ let now = verts.len();
+ verts.push([p.x.0 as f32, p.y.0 as f32]);
+
+ ids.push(idx);
+ ids.push(now);
+ ids.push(prev);
+ prev = now;
+ }
+ }
+
+ (verts.concat(), ids)
+}
+
+#[wasm_bindgen]
+pub struct Game {
+ states: Vec<types::State>,
+ turn: usize,
+
+ planet_map: HashMap<(String, String), Circle>,
+
+ /* put extra shit here */
+ view_box: Vec<f32>,
+
+ planets: Vec<f32>,
+ planet_ships: Vec<usize>,
+
+ ship_locations: Vec<f32>,
+ ship_label_locations: Vec<f32>,
+ ship_colours: Vec<f32>,
+ ship_counts: Vec<usize>,
+
+ current_planet_colours: Vec<f32>,
+
+ voronoi_vertices: Vec<f32>,
+ voronoi_colors: Vec<f32>,
+ voronoi_indices: Vec<usize>,
+}
+
+#[wasm_bindgen]
+impl Game {
+ pub fn new(file: &str) -> Self {
+ utils::set_panic_hook();
+
+ // First line is fucked but we just filter out things that cannot parse
+ let states: Vec<types::State> = file
+ .split("\n")
+ .filter_map(|line| serde_json::from_str(line).ok())
+ .collect();
+
+ let mut planet_map = HashMap::new();
+
+ // Iterator?
+ for p1 in states[0].planets.iter() {
+ for p2 in states[0].planets.iter() {
+ planet_map.insert((p1.name.clone(), p2.name.clone()), Circle::new(&p1, &p2));
+ }
+ }
+ let view_box = utils::caclulate_viewbox(&states[0].planets);
+
+ let (voronoi_vertices, voronoi_indices) =
+ create_voronoi(&states[0].planets, view_box[2].max(view_box[3]));
+
+ let voronoi_colors: Vec<f32> = voronoi_indices
+ .iter()
+ .map(|_| [0.0, 0.0, 0.0])
+ .collect::<Vec<[f32; 3]>>()
+ .concat(); // Init these colours on black
+
+ Self {
+ planets: utils::get_planets(&states[0].planets, 2.0),
+ planet_ships: Vec::new(),
+ view_box,
+
+ planet_map,
+ turn: 0,
+ states,
+ ship_locations: Vec::new(),
+ ship_label_locations: Vec::new(),
+ ship_colours: Vec::new(),
+ ship_counts: Vec::new(),
+ current_planet_colours: Vec::new(),
+
+ voronoi_vertices,
+ voronoi_indices,
+ voronoi_colors,
+ }
+ }
+
+ pub fn push_state(&mut self, state_str: &str) {
+ if let Ok(state) = serde_json::from_str(state_str) {
+ self.states.push(state);
+ }
+ }
+
+ pub fn get_viewbox(&self) -> Vec<f32> {
+ self.view_box.clone()
+ }
+
+ pub fn get_planets(&self) -> Vec<f32> {
+ self.planets.clone()
+ }
+
+ pub fn get_planet_ships(&self) -> Vec<usize> {
+ self.planet_ships.clone()
+ }
+
+ pub fn get_planet_colors(&self) -> Vec<f32> {
+ self.current_planet_colours.clone()
+ }
+
+ pub fn turn_count(&self) -> usize {
+ self.states.len()
+ }
+
+ pub fn update_turn(&mut self, turn: usize) -> usize {
+ self.turn = turn.min(self.states.len() - 1);
+
+ self.update_planet_ships();
+ self.update_planet_colours();
+ self.update_voronoi_colors();
+ self.update_ship_locations();
+ self.update_ship_counts();
+
+ self.turn
+ }
+
+ fn update_planet_ships(&mut self) {
+ self.planet_ships = self.states[self.turn]
+ .planets
+ .iter()
+ .map(|p| p.ship_count as usize)
+ .collect();
+ }
+
+ fn update_voronoi_colors(&mut self) {
+ for (i, p) in self.states[self.turn].planets.iter().enumerate() {
+ let color = utils::COLORS[p.owner.unwrap_or(0) as usize % utils::COLORS.len()];
+ self.voronoi_colors[i * 3 + 0] = color[0];
+ self.voronoi_colors[i * 3 + 1] = color[1];
+ self.voronoi_colors[i * 3 + 2] = color[2];
+ }
+ }
+
+ fn update_planet_colours(&mut self) {
+ let mut new_vec: Vec<[f32; 3]> = Vec::new();
+ let planets_now = self.states[self.turn].planets.iter();
+ let planets_later = self.states[(self.turn + 1).min(self.states.len() - 1)]
+ .planets
+ .iter();
+
+ for (p1, p2) in planets_now.zip(planets_later) {
+ new_vec
+ .push(utils::COLORS[p1.owner.unwrap_or(0) as usize % utils::COLORS.len()].into());
+ new_vec
+ .push(utils::COLORS[p2.owner.unwrap_or(0) as usize % utils::COLORS.len()].into());
+ }
+
+ self.current_planet_colours = new_vec.concat::<f32>();
+ }
+
+ fn update_ship_locations(&mut self) {
+ let mut new_sl = Vec::new();
+ let mut new_sll = Vec::new();
+
+ let t = Mat3::new(0.2, 0., 0., 0., 0.2, 0.0, 0., -0.5, 0.2);
+
+ for ship in self.states[self.turn].expeditions.iter() {
+ let ((o1, a1), (o2, a2)) = self
+ .planet_map
+ .get(&(ship.origin.clone(), ship.destination.clone()))
+ .unwrap()
+ .get_for_remaining(ship.turns_remaining as usize);
+ new_sl.push((o1 * Mat3::rotate_z(a1)).to_array());
+ new_sl.push((o2 * Mat3::rotate_z(a2)).to_array());
+
+ new_sll.push((o1 + t).to_array());
+ new_sll.push((o2 + t).to_array());
+ }
+
+ self.ship_locations = new_sl.concat();
+ self.ship_label_locations = new_sll.concat();
+
+ self.ship_colours = self.states[self.turn]
+ .expeditions
+ .iter()
+ .map(|s| utils::COLORS[s.owner as usize % utils::COLORS.len()])
+ .collect::<Vec<[f32; 3]>>()
+ .concat();
+ }
+
+ fn update_ship_counts(&mut self) {
+ self.ship_counts = self.states[self.turn]
+ .expeditions
+ .iter()
+ .map(|s| s.ship_count as usize)
+ .collect();
+ }
+
+ pub fn get_max_ships(&self) -> usize {
+ self.states
+ .iter()
+ .map(|s| s.expeditions.len())
+ .max()
+ .unwrap()
+ }
+
+ pub fn get_ship_locations(&self) -> Vec<f32> {
+ self.ship_locations.clone()
+ }
+
+ pub fn get_ship_label_locations(&self) -> Vec<f32> {
+ self.ship_label_locations.clone()
+ }
+
+ pub fn get_ship_colours(&self) -> Vec<f32> {
+ self.ship_colours.clone()
+ }
+
+ pub fn get_ship_counts(&self) -> Vec<usize> {
+ self.ship_counts.clone()
+ }
+
+ pub fn get_voronoi_verts(&self) -> Vec<f32> {
+ self.voronoi_vertices.clone()
+ }
+
+ pub fn get_voronoi_colours(&self) -> Vec<f32> {
+ self.voronoi_colors.clone()
+ }
+
+ pub fn get_voronoi_inds(&self) -> Vec<usize> {
+ self.voronoi_indices.clone()
+ }
+}
+
+#[wasm_bindgen]
+extern "C" {
+ fn alert(s: &str);
+ #[wasm_bindgen(js_namespace = console)]
+ fn log(s: &str);
+}
diff --git a/web/planetwars-rs/src/types.rs b/web/planetwars-rs/src/types.rs
new file mode 100644
index 0000000..2d7d8c0
--- /dev/null
+++ b/web/planetwars-rs/src/types.rs
@@ -0,0 +1,45 @@
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Expedition {
+ pub id: u64,
+ pub ship_count: u64,
+ pub origin: String,
+ pub destination: String,
+ pub owner: u64,
+ pub turns_remaining: u64,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Planet {
+ pub ship_count: u64,
+ pub x: f32,
+ pub y: f32,
+ pub owner: Option<u32>,
+ pub name: String,
+}
+
+use std::hash::{Hash, Hasher};
+use std::mem;
+
+impl Hash for Planet {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ unsafe {
+ let x: u32 = mem::transmute_copy(&self.x);
+ let y: u32 = mem::transmute_copy(&self.y);
+ state.write_u32(x);
+ state.write_u32(y);
+ }
+ }
+}
+
+impl PartialEq for Planet {
+ fn eq(&self, other: &Self) -> bool {
+ (self.x - other.x).abs() < 0.0001 && (self.y - other.y).abs() < 0.0001
+ }
+}
+impl Eq for Planet {}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct State {
+ pub planets: Vec<Planet>,
+ pub expeditions: Vec<Expedition>,
+}
diff --git a/web/planetwars-rs/src/utils.rs b/web/planetwars-rs/src/utils.rs
new file mode 100644
index 0000000..a903912
--- /dev/null
+++ b/web/planetwars-rs/src/utils.rs
@@ -0,0 +1,65 @@
+pub fn set_panic_hook() {
+ // When the `console_error_panic_hook` feature is enabled, we can call the
+ // `set_panic_hook` function at least once during initialization, and then
+ // we will get better error messages if our code ever panics.
+ //
+ // For more details see
+ // https://github.com/rustwasm/console_error_panic_hook#readme
+ #[cfg(feature = "console_error_panic_hook")]
+ console_error_panic_hook::set_once();
+}
+
+/// this is total extra, so it the planet viewbox is like 100px wide, it will now be in total 110 pixels wide
+static VIEWBOX_SCALE: f32 = 0.1;
+
+pub static COLORS: [[f32; 3]; 10] = [
+ [0.5, 0.5, 0.5],
+ [1.0, 0.50, 0.0], // #FF8000
+ [0.0, 0.50, 1.0], // #0080ff
+ [1.0, 0.4, 0.58], // #FF6693
+ [0.24, 0.79, 0.33], // #3fcb55
+ [0.79, 0.76, 0.24], // #cbc33f
+ [0.81, 0.25, 0.91], // #cf40e9
+ [0.94, 0.32, 0.32], // #FF3F0D
+ [0.11, 0.93, 0.94], // #1beef0
+ [0.05, 0.77, 1.0], // #0DC5FF
+];
+
+use super::types;
+
+pub fn caclulate_viewbox(planets: &Vec<types::Planet>) -> Vec<f32> {
+ let mut iter = planets.iter();
+
+ let init = match iter.next() {
+ Some(p) => (p.x, p.y, p.x, p.y),
+ None => return vec![0.0, 0.0, 0.0, 0.0],
+ };
+ let (min_x, min_y, max_x, max_y) =
+ planets
+ .iter()
+ .fold(init, |(min_x, min_y, max_x, max_y), p| {
+ (
+ min_x.min(p.x),
+ min_y.min(p.y),
+ max_x.max(p.x),
+ max_y.max(p.y),
+ )
+ });
+
+ let (width, height) = (max_x - min_x, max_y - min_y);
+ let (dx, dy) = (
+ (VIEWBOX_SCALE * width).max(6.0),
+ (VIEWBOX_SCALE * height).max(6.0),
+ );
+
+ vec![min_x - dx / 2.0, min_y - dy / 2.0, width + dx, height + dy]
+}
+
+pub fn get_planets(planets: &Vec<types::Planet>, r: f32) -> Vec<f32> {
+ planets.iter().fold(Vec::new(), |mut cum, p| {
+ cum.push(p.x);
+ cum.push(p.y);
+ cum.push(r);
+ cum
+ })
+}