#![allow(clippy::indexing_slicing)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::panic)]
use std::{
collections::{BinaryHeap, HashMap},
fmt::{Debug, Formatter},
hash::Hash,
time::Instant,
};
use crate::client::{
pathfind::{
traits::{GoalCheck, Heuristic, Progression, Progressor},
MinHeapNode,
},
timing::Increment,
};
const COEFFICIENTS: [f64; 7] = [1.5, 2.0, 2.5, 3., 4., 5., 10.];
const MIN_DIST: f64 = 5.0;
pub trait Node: Clone {
type Record: PartialEq + Hash + Eq + Clone;
fn get_record(&self) -> Self::Record;
}
pub struct AStar<T: Node> {
state: Option<AStarState<T>>,
}
struct AStarState<T: Node> {
idx_to_record: Vec<T::Record>,
record_to_idx: HashMap<T::Record, usize>,
parent_map: HashMap<usize, usize>,
g_scores: HashMap<usize, f64>,
open_set: BinaryHeap<MinHeapNode<T, f64>>,
valid: bool,
total_duration_ms: u128,
max_duration_ms: u128,
meta_heuristics: [f64; 7],
meta_heuristics_ids: [usize; 7],
}
fn reconstruct_path<T: Clone>(
vec: &[T],
goal_idx: usize,
parent_map: &HashMap<usize, usize>,
) -> Vec<T> {
let init_value = vec[goal_idx].clone();
let mut res = vec![init_value];
let mut on_idx = goal_idx;
while let Some(&next_idx) = parent_map.get(&on_idx) {
res.push(vec[next_idx].clone());
on_idx = next_idx;
}
res.reverse();
res
}
pub struct PathResult<T> {
pub complete: bool,
pub value: Vec<T>,
}
impl<T: Debug> Debug for PathResult<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"PathResult{{complete: {:?}, value: {:?}}}",
self.complete, self.value
))
}
}
impl<T> PathResult<T> {
fn complete(value: Vec<T>) -> Self {
Self {
complete: true,
value,
}
}
fn incomplete(value: Vec<T>) -> Self {
Self {
complete: false,
value,
}
}
}
impl<T: Node> AStar<T> {
pub fn new(init_node: T) -> Self {
let init_record = init_node.get_record();
let mut record_to_idx = HashMap::new();
record_to_idx.insert(init_record.clone(), 0);
let mut g_scores = HashMap::new();
g_scores.insert(0, 0.0);
let mut open_set = BinaryHeap::new();
open_set.push(MinHeapNode {
contents: init_node,
score: f64::MAX,
});
let state = Some(AStarState {
idx_to_record: vec![init_record],
meta_heuristics: [f64::MAX; 7],
record_to_idx,
g_scores,
open_set,
total_duration_ms: 0,
parent_map: nbt::Map::default(),
valid: false,
meta_heuristics_ids: [0; 7],
max_duration_ms: 5000,
});
Self { state }
}
pub fn set_max_millis(&mut self, value: u128) {
self.state.as_mut().unwrap().max_duration_ms = value;
}
pub fn select_best(&mut self) -> Increment<PathResult<T::Record>> {
let state = self.state.take().unwrap();
let mut best = (f64::MAX, 0);
for i in 0..7 {
let heuristic = state.meta_heuristics[i];
let id = state.meta_heuristics_ids[i];
if heuristic < best.0 {
best = (heuristic, id);
}
let g_score = state.g_scores[&id];
if g_score > MIN_DIST {
println!("larger than min dist");
let path = reconstruct_path(&state.idx_to_record, id, &state.parent_map);
return Increment::Finished(PathResult::incomplete(path));
}
}
let path = reconstruct_path(&state.idx_to_record, best.1, &state.parent_map);
Increment::Finished(PathResult::incomplete(path))
}
pub fn iterate_until(
&mut self,
end_at: Instant,
heuristic: &impl Heuristic<T>,
progressor: &impl Progressor<T>,
goal_check: &impl GoalCheck<T>,
) -> Increment<PathResult<T::Record>> {
let iter_start = Instant::now();
loop {
let now = Instant::now();
if now >= end_at {
let iter_duration = now.duration_since(iter_start);
let state = self.state.as_mut().unwrap();
let dur = &mut state.total_duration_ms;
*dur += iter_duration.as_millis();
return if *dur > state.max_duration_ms {
println!("reached maxed duration");
return self.select_best();
} else {
Increment::InProgress
};
}
match self.iterate(heuristic, progressor, goal_check) {
Increment::Finished(res) => {
return Increment::Finished(res);
}
Increment::InProgress => {}
}
}
}
pub fn iterate(
&mut self,
heuristic: &impl Heuristic<T>,
progressor: &impl Progressor<T>,
goal_check: &impl GoalCheck<T>,
) -> Increment<PathResult<T::Record>> {
let Some(state) = self.state.as_mut() else { panic!("called after finished") };
if let Some(node) = state.open_set.pop() {
let parent = node.contents;
if goal_check.is_goal(&parent) {
let record = parent.get_record();
let record_idx = state.record_to_idx[&record];
let state = self.state.take().unwrap();
let path = reconstruct_path(&state.idx_to_record, record_idx, &state.parent_map);
return Increment::Finished(PathResult::complete(path));
}
let Progression::Movements(neighbors) = progressor.progressions(&parent) else { return Increment::InProgress };
let parent_record = parent.get_record();
let parent_record_idx = state.record_to_idx[&parent_record];
let parent_g_score = *state.g_scores.get(&parent_record_idx).unwrap();
'neighbor_loop: for neighbor in neighbors {
let tentative_g_score = parent_g_score + neighbor.cost;
let value = neighbor.value.clone();
let record = value.get_record();
let (record_idx, _g_score) = if let Some(idx) = state.record_to_idx.get(&record) {
let prev_g_score = state.g_scores.get_mut(idx).unwrap();
if tentative_g_score < *prev_g_score {
*prev_g_score = tentative_g_score;
} else {
continue 'neighbor_loop;
}
(*idx, tentative_g_score)
} else {
let value_idx = state.idx_to_record.len();
state.idx_to_record.push(record.clone());
state.record_to_idx.insert(record, value_idx);
state.g_scores.insert(value_idx, tentative_g_score);
(value_idx, tentative_g_score)
};
state.parent_map.insert(record_idx, parent_record_idx);
let h_score = heuristic.heuristic(&neighbor.value);
let f_score = tentative_g_score + h_score;
#[allow(clippy::needless_range_loop)]
for i in 0..state.meta_heuristics.len() {
let meta_heuristic = h_score + tentative_g_score / COEFFICIENTS[i];
let current = state.meta_heuristics[i];
if meta_heuristic < current {
state.meta_heuristics[i] = meta_heuristic;
state.meta_heuristics_ids[i] = record_idx;
if !state.valid && tentative_g_score > MIN_DIST {
state.valid = true;
}
}
}
let heap_node = MinHeapNode {
contents: value,
score: f_score,
};
state.open_set.push(heap_node);
}
} else {
println!(
"no more nodes iterated through {}",
state.idx_to_record.len()
);
return self.select_best();
}
Increment::InProgress
}
}