diff --git a/src/bin/regionfilter.rs b/src/bin/regionfilter.rs index c944c6c..2b94e98 100644 --- a/src/bin/regionfilter.rs +++ b/src/bin/regionfilter.rs @@ -28,7 +28,6 @@ fn parse_coords(c: TapeObject) -> Result { for (k,v) in c { let v = v.as_f64().ok_or("Coord value must be f64")? as f32; - let v = (v * 32.) as i32; match k { "x" => pt.0 = v, "y" => pt.1 = v, @@ -114,13 +113,13 @@ fn main() -> anyhow::Result<()> { // Small region #[cfg(ignore)] let region = Box::from_corners( - Point(11900*32, -3800*32, -11510*32), - Point(17400*32, 3800*32, -6500*32), + Point(11900., -3800., -11510.), + Point(17400., 3800., -6500.), ); // Everything southeast of Haffner 18 LSS 27 let region = Box::from_corners( - Point(11900*32, -3800*32, -18510*32), - Point(64400*32, 3800*32, -6500*32), + Point(11900., -3800., -18510.), + Point(64400., 3800., -6500.), ); let mut reg_name = std::io::BufWriter::new(std::fs::File::create("region.nam")?); @@ -128,23 +127,8 @@ fn main() -> anyhow::Result<()> { let mut name_pos = 0; for mut sys in f.filter_map(id).filter(|sys| region.contains(sys.coords)) { - sys.name_off = name_pos as usize; - let hdr = [sys.name.len().min(255) as u8]; - if hdr[0] == 255 { - eprintln!("WARNING: Long system name {}", sys.name); - } - reg_name.write(&hdr[..])?; - reg_name.write(&sys.name.as_bytes()[..hdr[0] as usize])?; - name_pos += hdr[0] as u32 + 1; - - reg_data.write(&sys.coords.0 .to_be_bytes()[..])?; - reg_data.write(&sys.coords.1 .to_be_bytes()[..])?; - reg_data.write(&sys.coords.2 .to_be_bytes()[..])?; - - let rest = (sys.star_flags as u32) + (name_pos << 4); - reg_data.write(&rest.to_be_bytes()[..])?; + sys.write_name(&mut reg_name)?; + sys.write_to(&mut reg_data); } - - Ok(()) } diff --git a/src/bin/routefinder.rs b/src/bin/routefinder.rs index df21654..6acf050 100644 --- a/src/bin/routefinder.rs +++ b/src/bin/routefinder.rs @@ -1,13 +1,20 @@ -use std::{io::Cursor, cmp::{Ordering, Reverse}, collections::{VecDeque, BinaryHeap}}; +use std::{io::Cursor, cmp::{Ordering, Reverse}, collections::{VecDeque, BinaryHeap, HashMap}, hash::Hash}; use edalyze::bindata::*; use rstar::primitives::{PointWithData, GeomWithData}; +#[derive(Default)] +struct PrevLink { + system: usize, + distance: f32, + uses_jumponium: bool, +} + struct IndexData { cost: f32, reachable: bool, - last: usize, + last: PrevLink, sys_data: System, } @@ -16,7 +23,7 @@ impl From for IndexData { Self { cost: f32::INFINITY, reachable: false, - last: 0, + last: PrevLink::default(), sys_data: sys, } } @@ -26,11 +33,14 @@ struct SearchNode { id: usize, cost: f32, heuristic: f32, + last: (usize, bool) } trait SearchState { + type Id; fn cost(&self) -> f32; fn heuristic(&self) -> f32; + fn id(&self) -> Self::Id; } // ordering tuned for BinaryHeap @@ -53,11 +63,28 @@ impl PartialEq for SearchNode { } impl Eq for SearchNode {} +impl SearchState for SearchNode { + type Id = usize; + + fn cost(&self) -> f32 { + self.cost + } + + fn heuristic(&self) -> f32 { + self.heuristic + } + + fn id(&self) -> Self::Id { + self.id + } +} + trait SearchQueue { fn q_next(&mut self) -> Option; fn q_empty(&self) -> bool; fn q_push(&mut self, item: T); fn q_clear(&mut self); + fn q_len(&self) -> usize; } struct SearchEnv { @@ -68,6 +95,7 @@ struct SearchEnv { target_system: usize, jumponium_cost_factor: f32, queue: Q, + visits: usize, } impl> SearchEnv { @@ -75,13 +103,13 @@ impl> SearchEnv { for item in self.meta.iter_mut() { item.cost = f32::INFINITY; item.reachable = false; - item.last = 0; } self.queue.q_clear(); let init_node = SearchNode{ id: self.init_system, - cost: 0, + cost: 0., heuristic: self.heuristic(self.init_system), + last: (self.init_system, false), }; self.queue.q_push(init_node); } @@ -89,8 +117,34 @@ impl> SearchEnv { fn is_done(&self) -> bool { self.queue.q_empty() || self.meta[self.target_system].reachable } - fn search(&mut self) { - // + fn search(&mut self) -> Option { + self.reset(); + eprintln!(""); + while let Some(node) = self.queue.q_next() { + // check whether node should be re-expanded + { + let scoord = self.meta[node.last.0].sys_data.coords; + let n = &mut self.meta[node.id]; + if n.reachable && n.cost < node.cost { + continue; + } + n.cost = node.cost; + n.reachable = true; + n.last = PrevLink { + uses_jumponium: node.last.1, + system: node.last.0, + distance: n.sys_data.coords.distance(scoord), + }; + } + self.visits += 1; + if self.visits % 1000 == 0 { + eprintln!("\x1b[1A\x1b[K{}k ({} in queue)", self.visits/100, self.queue.q_len()); + } + self.visit_star(node.id, node.cost); + } + + let tsys = &self.meta[self.target_system]; + tsys.reachable.then_some(tsys.cost) } fn heuristic(&self, sys_id: usize) -> f32 { @@ -100,16 +154,32 @@ impl> SearchEnv { (dist - self.jdist(sys_id)) / (4. * self.base_jump) } - fn visit_star(&self, sys_id: usize, base_cost: f32) { + fn visit_star(&mut self, sys_id: usize, base_cost: f32) { let star_jump = self.jdist(sys_id); - let star_jump_sq = self.boosted_jump * self.boosted_jump; + let star_jump_sq = star_jump * star_jump; let base_jump_sq = self.base_jump * self.base_jump; let augm_jump_sq = base_jump_sq * 4.; let max_jump_sq = augm_jump_sq.max(star_jump_sq); + //eprintln!("Max jump: {max_jump_sq}"); let cur_pos = self.meta[sys_id].sys_data.coords; - for n_star in self.rst.locate_within_distance(cur_pos, (max_jump_sq * 1024.).ceil() as i32) { + for n_star in self.rst.locate_within_distance(cur_pos, max_jump_sq) { + //eprintln!("Examining {}", self.meta[n_star.data].sys_data.name); + let dist_sq = n_star.geom().distance_sq(cur_pos); + let (cost_factor, _range, uses_jumponium) = if dist_sq > max_jump_sq { + continue; + } else if dist_sq < star_jump_sq { + (1., star_jump, false) + } else { + // must be a jumponium jump + (self.jumponium_cost_factor, 2. * self.base_jump, true) + }; + + // TODO: make an estimate of the fuel used + let cost = base_cost + cost_factor; + let heuristic = self.heuristic(n_star.data); + self.queue.q_push(SearchNode { id: n_star.data, cost, heuristic, last: (sys_id, uses_jumponium) }); } } @@ -125,17 +195,74 @@ impl SearchQueue for BFS { fn q_empty(&self) -> bool { self.0.is_empty() } fn q_push(&mut self, item: T) { self.0.push_back(item); } fn q_clear(&mut self) { self.0.clear(); } + fn q_len(&self) -> usize { self.0.len() } } impl Default for BFS { fn default() -> Self { BFS(VecDeque::new()) } } +struct CostCompare(T); +impl Ord for CostCompare { + fn cmp(&self, other: &Self) -> Ordering { + f32::total_cmp(&self.0.cost(), &other.0.cost()).reverse() + } +} +impl PartialOrd for CostCompare { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Eq for CostCompare { +} +impl PartialEq for CostCompare { + fn eq(&self, other: &Self) -> bool { + self.cmp(other) == Ordering::Equal + } +} + + +struct BFS1{ + q: BinaryHeap>, + seen: HashMap, +} + +impl SearchQueue for BFS1 +where T::Id: Hash+Eq { + fn q_next(&mut self) -> Option { self.q.pop().map(|x| x.0) } + fn q_empty(&self) -> bool { self.q.is_empty() } + fn q_push(&mut self, item: T) { + let id = item.id(); + if let Some(odist) = self.seen.get(&id) { + if *odist < item.cost() { + return; + } + } + self.seen.insert(id, item.cost()); + self.q.push(CostCompare(item)); + } + fn q_clear(&mut self) { + self.q.clear(); + self.seen.clear(); + } + fn q_len(&self) -> usize { self.q.len() } +} +impl Default for BFS1 { + fn default() -> Self { + BFS1{ + q: BinaryHeap::new(), + seen: HashMap::new(), + } + } +} + + struct DFS(Vec); impl SearchQueue for DFS { fn q_next(&mut self) -> Option { self.0.pop() } fn q_empty(&self) -> bool { self.0.is_empty() } fn q_push(&mut self, item: T) { self.0.push(item); } fn q_clear(&mut self) { self.0.clear(); } + fn q_len(&self) -> usize { self.0.len() } } impl Default for DFS { fn default() -> Self { DFS(Vec::new()) } @@ -147,14 +274,26 @@ impl SearchQueue for AStar { fn q_empty(&self) -> bool { self.0.is_empty() } fn q_push(&mut self, item: T) { self.0.push(Reverse(item)); } fn q_clear(&mut self) { self.0.clear(); } + fn q_len(&self) -> usize { self.0.len() } } impl Default for AStar { fn default() -> Self { AStar(BinaryHeap::new()) } } +use structopt::StructOpt; + +#[derive(StructOpt)] +struct Options { + #[structopt(short="r", default_value="10")] + jump_range: f32, + #[structopt(short="j", default_value="10")] + cost_factor: f32, +} fn main() -> anyhow::Result<()> { + let opts = Options::from_args(); + let mut reg_dat = Cursor::new(std::fs::read("region.dat")?); let mut reg_nam = Cursor::new(std::fs::read("region.nam")?); @@ -183,13 +322,48 @@ fn main() -> anyhow::Result<()> { .0; let mut env = SearchEnv { - rst,meta: systems,base_jump: 10., init_system, target_system, - queue: BFS::default() + rst, + meta: systems, + base_jump: opts.jump_range, + init_system, + target_system, + queue: BFS1::default(), + jumponium_cost_factor: opts.cost_factor, + visits: 0, }; + if let Some(cost) = env.search() { + eprintln!("Reached in {cost}"); + + // produce a trace... + let mut cur_id = env.target_system; + let mut stack = vec![]; + let mut total_dist = 0.; + while cur_id != env.init_system { + let sys = &env.meta[cur_id]; + stack.push((&sys.sys_data.name, sys.last.distance, sys.last.uses_jumponium)); + total_dist += sys.last.distance; + cur_id = sys.last.system; + } + + + let mut jcount = 0; + for (name, dist, usej) in stack.iter().rev().copied() { + eprintln!(" {name:-40}: {} {dist:3.0}", if usej { "j" } else { "-" }); + if usej { + jcount += 1; + } + } + let opt_dist = env.meta[env.init_system].sys_data.coords.distance(env.meta[env.target_system].sys_data.coords); + let eff = 100. * opt_dist / total_dist; + eprintln!("Total distance: {total_dist} ({eff:5.2}% eff)"); + eprintln!("{jcount} boosts used"); + + + } else { + eprintln!("Unreachable after {} visits", env.visits); + std::process::exit(1); + } - env.queue.q_push(SearchNode{ - heuristic: 0., cost: 0., id: 0 - }); diff --git a/src/bindata.rs b/src/bindata.rs index 8ecf028..aa2a58c 100644 --- a/src/bindata.rs +++ b/src/bindata.rs @@ -6,20 +6,20 @@ pub const IS_FUEL: u8 = 1; pub const IS_WDRF: u8 = 2; pub const IS_NEUT: u8 = 4; -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] -pub struct Point(pub i32, pub i32, pub i32); +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct Point(pub f32, pub f32, pub f32); #[derive(Copy, Clone, Debug)] pub struct Box(pub Point, pub Point); -const fn isortp(a: i32, b: i32) -> (i32, i32) { +fn fsortp(a: f32, b: f32) -> (f32, f32) { if a < b { (a,b) } else { (b,a) } } impl Box { - pub const fn from_corners(x: Point, y: Point) -> Box { - let (ax, bx) = isortp(x.0, y.0); - let (ay, by) = isortp(x.1, y.1); - let (az, bz) = isortp(x.2, y.2); + pub fn from_corners(x: Point, y: Point) -> Box { + let (ax, bx) = fsortp(x.0, y.0); + let (ay, by) = fsortp(x.1, y.1); + let (az, bz) = fsortp(x.2, y.2); let pt1 = Point(ax, ay, az); let pt2 = Point(bx, by, bz); @@ -33,7 +33,6 @@ impl Box { (self.0 .1 .. self.1 .1).contains(&x.1) && (self.0 .2 .. self.1 .2).contains(&x.2); } - } #[derive(Default)] @@ -53,18 +52,18 @@ impl System { return Ok(name_len + 1); } pub fn write_to(&self, writer: &mut dyn Write) -> io::Result<()> { - writer.write_i32::(self.coords.0)?; - writer.write_i32::(self.coords.1)?; - writer.write_i32::(self.coords.2)?; + writer.write_i32::((self.coords.0 * 32.) as i32)?; + writer.write_i32::((self.coords.1 * 32.) as i32)?; + writer.write_i32::((self.coords.2 * 32.) as i32)?; let rest = self.star_flags as u32 + ((self.name_off as u32) << 4); writer.write_u32::(rest)?; Ok(()) } pub fn read_from(r: &mut dyn Read) -> io::Result { - let x = r.read_i32::()?; - let y = r.read_i32::()?; - let z = r.read_i32::()?; + let x = r.read_i32::()? as f32 / 32.; + let y = r.read_i32::()? as f32 / 32.; + let z = r.read_i32::()? as f32 / 32.; let rest = r.read_u32::()?; let name_off = (rest >> 4) as usize; let star_flags = (rest & 0xF) as u8; @@ -102,7 +101,7 @@ impl System { } impl rstar::Point for Point { - type Scalar = i32; + type Scalar = f32; const DIMENSIONS: usize = 3; fn generate(mut gen: impl FnMut(usize) -> Self::Scalar) -> Self { let x = gen(0); @@ -134,9 +133,9 @@ impl Point { pub fn distance_sq(&self, other: Self) -> f32 { let Point(sx, sy, sz) = *self; let Point(tx, ty, tz) = other; - let dx = sx as f32 - tx as f32; - let dy = sy as f32 - ty as f32; - let dz = sz as f32 - tz as f32; + let dx = sx - tx; + let dy = sy - ty; + let dz = sz - tz; (dx*dx) + (dy*dy) + (dz*dz) }