use super::registry::WorldMapRegistry;
use super::types::*;
use crate::service::Service;
use async_trait::async_trait;
use std::any::Any;
use std::collections::{BinaryHeap, HashMap, HashSet};
#[derive(Clone, Default)]
pub struct WorldMapService;
#[async_trait]
impl Service for WorldMapService {
fn name(&self) -> &'static str {
"issun:worldmap_service"
}
fn clone_box(&self) -> Box<dyn Service> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl WorldMapService {
pub fn calculate_duration(distance: f32, speed: f32, terrain_multiplier: f32) -> f32 {
if speed * terrain_multiplier > 0.0 {
distance / (speed * terrain_multiplier)
} else {
f32::MAX
}
}
pub fn find_path(
from: &LocationId,
to: &LocationId,
registry: &WorldMapRegistry,
) -> Option<Vec<RouteId>> {
if from == to {
return Some(Vec::new());
}
let mut queue: BinaryHeap<PathNode> = BinaryHeap::new();
let mut distances: HashMap<LocationId, f32> = HashMap::new();
let mut previous: HashMap<LocationId, RouteId> = HashMap::new();
let mut visited: HashSet<LocationId> = HashSet::new();
distances.insert(from.clone(), 0.0);
queue.push(PathNode {
distance: 0.0,
location_id: from.clone(),
});
while let Some(PathNode {
distance: current_dist,
location_id: current,
}) = queue.pop()
{
if visited.contains(¤t) {
continue;
}
visited.insert(current.clone());
if ¤t == to {
break;
}
let routes = registry.get_routes_from(¤t);
for route in routes {
let neighbor = if route.from == current {
&route.to
} else if route.bidirectional {
&route.from
} else {
continue;
};
if visited.contains(neighbor) {
continue;
}
let edge_dist = if let Some(explicit) = route.distance {
explicit
} else if let (Some(from_loc), Some(to_loc)) = (
registry.get_location(&route.from),
registry.get_location(&route.to),
) {
from_loc.position.distance_to(&to_loc.position)
} else {
continue;
};
let new_dist = current_dist + edge_dist;
let neighbor_dist = *distances.get(neighbor).unwrap_or(&f32::MAX);
if new_dist < neighbor_dist {
distances.insert(neighbor.clone(), new_dist);
previous.insert(neighbor.clone(), route.id.clone());
queue.push(PathNode {
distance: new_dist,
location_id: neighbor.clone(),
});
}
}
}
if !previous.contains_key(to) {
return None; }
let mut path = Vec::new();
let mut current = to.clone();
while let Some(route_id) = previous.get(¤t) {
path.push(route_id.clone());
let route = registry.get_route(route_id)?;
current = if route.to == current {
route.from.clone()
} else {
route.to.clone()
};
if ¤t == from {
break;
}
}
path.reverse();
Some(path)
}
pub fn calculate_encounter_chance(
progress: f32,
danger_level: f32,
base_rate: f32,
danger_multiplier: f32,
) -> f32 {
(base_rate * (1.0 + danger_level * danger_multiplier) * progress).min(1.0)
}
pub fn calculate_path_distance(path: &[RouteId], registry: &WorldMapRegistry) -> f32 {
path.iter()
.filter_map(|route_id| {
let route = registry.get_route(route_id)?;
if let Some(dist) = route.distance {
Some(dist)
} else {
let from_loc = registry.get_location(&route.from)?;
let to_loc = registry.get_location(&route.to)?;
Some(from_loc.position.distance_to(&to_loc.position))
}
})
.sum()
}
}
#[derive(Clone, Debug, PartialEq)]
struct PathNode {
distance: f32,
location_id: LocationId,
}
impl Eq for PathNode {}
impl PartialOrd for PathNode {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PathNode {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other
.distance
.partial_cmp(&self.distance)
.unwrap_or(std::cmp::Ordering::Equal)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calculate_duration() {
let duration = WorldMapService::calculate_duration(100.0, 10.0, 1.0);
assert_eq!(duration, 10.0);
let duration = WorldMapService::calculate_duration(100.0, 10.0, 0.5);
assert_eq!(duration, 20.0);
let duration = WorldMapService::calculate_duration(100.0, 0.0, 1.0);
assert_eq!(duration, f32::MAX);
}
#[test]
fn test_find_path_direct() {
let mut registry = WorldMapRegistry::new();
registry.register_location(
Location::new("city_a", "City A").with_position(Position::new(0.0, 0.0)),
);
registry.register_location(
Location::new("city_b", "City B").with_position(Position::new(100.0, 0.0)),
);
registry.register_route(Route::new("route_1", "city_a", "city_b"));
let path =
WorldMapService::find_path(&"city_a".to_string(), &"city_b".to_string(), ®istry);
assert!(path.is_some());
let path = path.unwrap();
assert_eq!(path.len(), 1);
assert_eq!(path[0], "route_1");
}
#[test]
fn test_find_path_multi_hop() {
let mut registry = WorldMapRegistry::new();
registry.register_location(
Location::new("city_a", "City A").with_position(Position::new(0.0, 0.0)),
);
registry.register_location(
Location::new("city_b", "City B").with_position(Position::new(50.0, 0.0)),
);
registry.register_location(
Location::new("city_c", "City C").with_position(Position::new(100.0, 0.0)),
);
registry.register_route(Route::new("route_1", "city_a", "city_b"));
registry.register_route(Route::new("route_2", "city_b", "city_c"));
let path =
WorldMapService::find_path(&"city_a".to_string(), &"city_c".to_string(), ®istry);
assert!(path.is_some());
let path = path.unwrap();
assert_eq!(path.len(), 2);
assert_eq!(path[0], "route_1");
assert_eq!(path[1], "route_2");
}
#[test]
fn test_find_path_no_route() {
let mut registry = WorldMapRegistry::new();
registry.register_location(Location::new("city_a", "City A"));
registry.register_location(Location::new("city_b", "City B"));
let path =
WorldMapService::find_path(&"city_a".to_string(), &"city_b".to_string(), ®istry);
assert!(path.is_none());
}
#[test]
fn test_find_path_same_location() {
let mut registry = WorldMapRegistry::new();
registry.register_location(Location::new("city_a", "City A"));
let path =
WorldMapService::find_path(&"city_a".to_string(), &"city_a".to_string(), ®istry);
assert!(path.is_some());
assert_eq!(path.unwrap().len(), 0);
}
#[test]
fn test_calculate_encounter_chance() {
let chance = WorldMapService::calculate_encounter_chance(0.5, 0.5, 0.1, 2.0);
assert_eq!(chance, 0.1);
let chance = WorldMapService::calculate_encounter_chance(1.0, 1.0, 0.5, 2.0);
assert_eq!(chance, 1.0);
}
#[test]
fn test_calculate_path_distance() {
let mut registry = WorldMapRegistry::new();
registry.register_location(
Location::new("city_a", "City A").with_position(Position::new(0.0, 0.0)),
);
registry.register_location(
Location::new("city_b", "City B").with_position(Position::new(50.0, 0.0)),
);
registry.register_location(
Location::new("city_c", "City C").with_position(Position::new(100.0, 0.0)),
);
registry.register_route(Route::new("route_1", "city_a", "city_b").with_distance(50.0));
registry.register_route(Route::new("route_2", "city_b", "city_c").with_distance(50.0));
let path = vec!["route_1".to_string(), "route_2".to_string()];
let distance = WorldMapService::calculate_path_distance(&path, ®istry);
assert_eq!(distance, 100.0);
}
}