Better track routing for civsim

This commit is contained in:
Joshua Barretto 2020-03-27 18:52:28 +00:00
parent fe4418bc0d
commit 348003fc1a
3 changed files with 84 additions and 38 deletions

View File

@ -52,7 +52,8 @@ pub struct Astar<S: Clone + Eq + Hash> {
cheapest_scores: HashMap<S, f32>, cheapest_scores: HashMap<S, f32>,
final_scores: HashMap<S, f32>, final_scores: HashMap<S, f32>,
visited: HashSet<S>, visited: HashSet<S>,
lowest_cost: Option<S>, cheapest_node: Option<S>,
cheapest_cost: Option<f32>,
} }
impl<S: Clone + Eq + Hash> Astar<S> { impl<S: Clone + Eq + Hash> Astar<S> {
@ -69,7 +70,8 @@ impl<S: Clone + Eq + Hash> Astar<S> {
cheapest_scores: std::iter::once((start.clone(), 0.0)).collect(), cheapest_scores: std::iter::once((start.clone(), 0.0)).collect(),
final_scores: std::iter::once((start.clone(), heuristic(&start))).collect(), final_scores: std::iter::once((start.clone(), heuristic(&start))).collect(),
visited: std::iter::once(start).collect(), visited: std::iter::once(start).collect(),
lowest_cost: None, cheapest_node: None,
cheapest_cost: None,
} }
} }
@ -86,11 +88,12 @@ impl<S: Clone + Eq + Hash> Astar<S> {
{ {
let iter_limit = self.max_iters.min(self.iter + iters); let iter_limit = self.max_iters.min(self.iter + iters);
while self.iter < iter_limit { while self.iter < iter_limit {
if let Some(PathEntry { node, .. }) = self.potential_nodes.pop() { if let Some(PathEntry { node, cost }) = self.potential_nodes.pop() {
self.cheapest_cost = Some(cost);
if satisfied(&node) { if satisfied(&node) {
return PathResult::Path(self.reconstruct_path_to(node)); return PathResult::Path(self.reconstruct_path_to(node));
} else { } else {
self.lowest_cost = Some(node.clone()); self.cheapest_node = Some(node.clone());
for neighbor in neighbors(&node) { for neighbor in neighbors(&node) {
let node_cheapest = self.cheapest_scores.get(&node).unwrap_or(&f32::MAX); let node_cheapest = self.cheapest_scores.get(&node).unwrap_or(&f32::MAX);
let neighbor_cheapest = let neighbor_cheapest =
@ -114,7 +117,7 @@ impl<S: Clone + Eq + Hash> Astar<S> {
} }
} else { } else {
return PathResult::None( return PathResult::None(
self.lowest_cost self.cheapest_node
.clone() .clone()
.map(|lc| self.reconstruct_path_to(lc)) .map(|lc| self.reconstruct_path_to(lc))
.unwrap_or_default(), .unwrap_or_default(),
@ -126,7 +129,7 @@ impl<S: Clone + Eq + Hash> Astar<S> {
if self.iter >= self.max_iters { if self.iter >= self.max_iters {
PathResult::Exhausted( PathResult::Exhausted(
self.lowest_cost self.cheapest_node
.clone() .clone()
.map(|lc| self.reconstruct_path_to(lc)) .map(|lc| self.reconstruct_path_to(lc))
.unwrap_or_default(), .unwrap_or_default(),
@ -136,6 +139,10 @@ impl<S: Clone + Eq + Hash> Astar<S> {
} }
} }
pub fn get_cheapest_cost(&self) -> Option<f32> {
self.cheapest_cost
}
fn reconstruct_path_to(&mut self, end: S) -> Path<S> { fn reconstruct_path_to(&mut self, end: S) -> Path<S> {
let mut path = vec![end.clone()]; let mut path = vec![end.clone()];
let mut cnode = &end; let mut cnode = &end;

View File

@ -39,7 +39,8 @@ const INITIAL_CIV_COUNT: usize = 20;
pub struct Civs { pub struct Civs {
civs: Store<Civ>, civs: Store<Civ>,
places: Store<Place>, places: Store<Place>,
routes: HashMap<(Id<Place>, Id<Place>), Route>, tracks: Store<Track>,
track_map: HashMap<Id<Place>, HashMap<Id<Place>, Id<Track>>>,
} }
struct GenCtx<'a, R: Rng> { struct GenCtx<'a, R: Rng> {
@ -62,8 +63,8 @@ impl Civs {
} }
// Temporary! // Temporary!
for route in this.routes.values() { for track in this.tracks.iter() {
for loc in route.path.iter() { for loc in track.path.iter() {
sim.get_mut(*loc).unwrap().place = Some(this.civs.iter().next().unwrap().homeland); sim.get_mut(*loc).unwrap().place = Some(this.civs.iter().next().unwrap().homeland);
} }
} }
@ -71,6 +72,35 @@ impl Civs {
this this
} }
/// Return the direct track between two places
fn track_between(&self, a: Id<Place>, b: Id<Place>) -> Option<Id<Track>> {
self.track_map
.get(&a)
.and_then(|dests| dests.get(&b))
.or_else(|| self.track_map
.get(&b)
.and_then(|dests| dests.get(&a)))
.copied()
}
/// Find the cheapest route between two places
fn route_between(&self, a: Id<Place>, b: Id<Place>) -> Option<(Path<Id<Place>>, f32)> {
let heuristic = move |p: &Id<Place>| (self.places.get(*p).center.distance_squared(self.places.get(b).center) as f32).sqrt();
let neighbors = |p: &Id<Place>| {
let p = *p;
let to = self.track_map.get(&p).map(|dests| dests.keys()).into_iter().flatten();
let fro = self.track_map.iter().filter(move |(_, dests)| dests.contains_key(&p)).map(|(p, _)| p);
to.chain(fro).filter(|p| **p != a).copied()
};
let transition = |a: &Id<Place>, b: &Id<Place>| self.tracks.get(self.track_between(*a, *b).unwrap()).cost;
let satisfied = |p: &Id<Place>| *p == b;
let mut astar = Astar::new(100, a, heuristic);
astar
.poll(100, heuristic, neighbors, transition, satisfied)
.into_path()
.and_then(|path| astar.get_cheapest_cost().map(|cost| (path, cost)))
}
fn birth_civ(&mut self, ctx: &mut GenCtx<impl Rng>) -> Option<Id<Civ>> { fn birth_civ(&mut self, ctx: &mut GenCtx<impl Rng>) -> Option<Id<Civ>> {
const CIV_BIRTHPLACE_AREA: Range<usize> = 64..256; const CIV_BIRTHPLACE_AREA: Range<usize> = 64..256;
let place = attempt(5, || { let place = attempt(5, || {
@ -112,34 +142,39 @@ impl Civs {
return None; return None;
} }
let place = self.places.insert(Place {
center: loc,
});
// Find neighbors // Find neighbors
const MAX_NEIGHBOR_DISTANCE: f32 = 100.0; const MAX_NEIGHBOR_DISTANCE: f32 = 250.0;
let mut nearby = self.places let mut nearby = self.places
.iter_ids() .iter_ids()
.map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt())) .map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt()))
.filter(|(p, dist)| *dist < MAX_NEIGHBOR_DISTANCE) .filter(|(p, dist)| *dist < MAX_NEIGHBOR_DISTANCE)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
nearby.sort_by_key(|(_, dist)| -*dist as i32); nearby.sort_by_key(|(_, dist)| *dist as i32);
let route_count = ctx.rng.gen_range(1, 3);
let neighbors = nearby
.into_iter()
.map(|(p, _)| p)
.filter_map(|p| if let Some(path) = find_path(ctx, loc, self.places.get(p).center) {
Some((p, path))
} else {
None
})
.take(route_count)
.collect::<Vec<_>>();
let place = self.places.insert(Place { for (nearby, _) in nearby.into_iter().take(ctx.rng.gen_range(3, 5)) {
center: loc, // Find a novel path
neighbors: neighbors.iter().map(|(p, _)| *p).collect(), if let Some((path, cost)) = find_path(ctx, loc, self.places.get(nearby).center) {
}); // Find a path using existing paths
if self
// Insert routes to neighbours into route list .route_between(place, nearby)
for (p, path) in neighbors { // If the novel path isn't efficient compared to existing routes, don't use it
self.routes.insert((place, p), Route { path }); .filter(|(_, route_cost)| *route_cost < cost * 3.0)
.is_none()
{
let track = self.tracks.insert(Track {
cost,
path,
});
self.track_map
.entry(place)
.or_default()
.insert(nearby, track);
}
}
} }
// Write place to map // Write place to map
@ -154,7 +189,7 @@ impl Civs {
} }
/// Attempt to find a path between two locations /// Attempt to find a path between two locations
fn find_path(ctx: &mut GenCtx<impl Rng>, a: Vec2<i32>, b: Vec2<i32>) -> Option<Path<Vec2<i32>>> { fn find_path(ctx: &mut GenCtx<impl Rng>, a: Vec2<i32>, b: Vec2<i32>) -> Option<(Path<Vec2<i32>>, f32)> {
let sim = &ctx.sim; let sim = &ctx.sim;
let heuristic = move |l: &Vec2<i32>| (l.distance_squared(b) as f32).sqrt(); let heuristic = move |l: &Vec2<i32>| (l.distance_squared(b) as f32).sqrt();
let neighbors = |l: &Vec2<i32>| { let neighbors = |l: &Vec2<i32>| {
@ -163,9 +198,11 @@ fn find_path(ctx: &mut GenCtx<impl Rng>, a: Vec2<i32>, b: Vec2<i32>) -> Option<P
}; };
let transition = |a: &Vec2<i32>, b: &Vec2<i32>| 1.0 + walk_in_dir(sim, *a, *b - *a).unwrap_or(10000.0); let transition = |a: &Vec2<i32>, b: &Vec2<i32>| 1.0 + walk_in_dir(sim, *a, *b - *a).unwrap_or(10000.0);
let satisfied = |l: &Vec2<i32>| *l == b; let satisfied = |l: &Vec2<i32>| *l == b;
Astar::new(5000, a, heuristic) let mut astar = Astar::new(20000, a, heuristic);
.poll(5000, heuristic, neighbors, transition, satisfied) astar
.poll(20000, heuristic, neighbors, transition, satisfied)
.into_path() .into_path()
.and_then(|path| astar.get_cheapest_cost().map(|cost| (path, cost)))
} }
/// Return true if travel between a location and a chunk next to it is permitted (TODO: by whom?) /// Return true if travel between a location and a chunk next to it is permitted (TODO: by whom?)
@ -175,7 +212,7 @@ fn walk_in_dir(sim: &WorldSim, a: Vec2<i32>, dir: Vec2<i32>) -> Option<f32> {
{ {
let a_alt = sim.get(a)?.alt; let a_alt = sim.get(a)?.alt;
let b_alt = sim.get(a + dir)?.alt; let b_alt = sim.get(a + dir)?.alt;
Some((b_alt - a_alt).max(0.0).powf(2.0).abs() / 50.0) Some(0.5 + (b_alt - a_alt).max(-0.5).abs() / 5.0)
} else { } else {
None None
} }
@ -240,9 +277,11 @@ pub struct Civ {
pub struct Place { pub struct Place {
center: Vec2<i32>, center: Vec2<i32>,
neighbors: Vec<Id<Place>>,
} }
pub struct Route { pub struct Track {
/// Cost of using this track relative to other paths. This cost is an arbitrary unit and
/// doesn't make sense unless compared to other track costs.
cost: f32,
path: Path<Vec2<i32>>, path: Path<Vec2<i32>>,
} }

View File

@ -65,8 +65,8 @@ use vek::*;
// don't think we actually cast a chunk id to float, just coordinates... could // don't think we actually cast a chunk id to float, just coordinates... could
// be wrong though! // be wrong though!
pub const WORLD_SIZE: Vec2<usize> = Vec2 { pub const WORLD_SIZE: Vec2<usize> = Vec2 {
x: 256 * 1, x: 512 * 1,
y: 256 * 1, y: 512 * 1,
}; };
/// A structure that holds cached noise values and cumulative distribution /// A structure that holds cached noise values and cumulative distribution