use crate::helpers::node_distance;
use crate::helpers::node_neighbours_offset;
use crate::helpers::offset_to_cubic;
use crate::HexOrientation;
use ::std::collections::HashMap;
use core::panic;
#[allow(clippy::too_many_arguments)]
pub fn astar_path(
start_node: (i32, i32),
nodes: HashMap<(i32, i32), f32>,
end_node: (i32, i32),
min_column: i32,
max_column: i32,
min_row: i32,
max_row: i32,
orientation: HexOrientation,
) -> Vec<(i32, i32)> {
if !nodes.contains_key(&start_node) {
panic!(
"Node data does not contain start node ({},{})",
start_node.0, start_node.1
);
}
if !nodes.contains_key(&end_node) {
panic!(
"Node data does not contain end node ({},{})",
end_node.0, end_node.1
);
}
if start_node.0 >= max_column
|| start_node.0 <= min_column
|| start_node.1 >= max_row
|| start_node.1 <= min_row
{
panic!("Start node is outside of searchable grid")
}
if end_node.0 >= max_column
|| end_node.0 <= min_column
|| end_node.1 >= max_row
|| end_node.1 <= min_row
{
panic!("End node is outside of searchable grid")
}
let mut nodes_weighted: HashMap<(i32, i32), (f32, f32)> = HashMap::new();
for (k, v) in nodes.iter() {
nodes_weighted.insert(
k.to_owned(),
(
v.to_owned(),
calculate_node_weight(k, &end_node, &orientation),
),
);
}
let start_weight: f32 = match nodes_weighted.get(&start_node) {
Some(x) => x.1,
None => panic!("Unable to find node weight"),
};
let mut node_astar_scores: HashMap<(i32, i32), f32> = HashMap::new();
node_astar_scores.insert(start_node, start_weight);
let mut queue = vec![(
start_node,
start_weight, Vec::<(i32, i32)>::new(),
0.0,
)];
while queue[0].0 != end_node {
let current_path = queue.swap_remove(0);
let available_nodes = node_neighbours_offset(
current_path.0,
&orientation,
min_column,
max_column,
min_row,
max_row,
);
for n in available_nodes.iter() {
let previous_complexities: f32 = current_path.3;
let current_node_complexity: f32 = match nodes_weighted.get(¤t_path.0) {
Some(x) => x.0 * 0.5,
None => panic!("Unable to find current node complexity for {:?}", &n),
};
let target_node_complexity: f32 = match nodes_weighted.get(n) {
Some(x) => x.0 * 0.5,
None => panic!("Unable to find target node complexity for {:?}", &n),
};
let complexity =
previous_complexities + target_node_complexity + current_node_complexity;
let target_weight: f32 = match nodes_weighted.get(n) {
Some(x) => x.1,
None => panic!("Unable to find node weight for {:?}", &n),
};
let astar = a_star_score(complexity, target_weight);
let mut previous_nodes_traversed = current_path.2.clone();
previous_nodes_traversed.push(current_path.0);
if node_astar_scores.contains_key(n) {
if node_astar_scores.get(n) >= Some(&astar) {
node_astar_scores.insert(*n, astar);
let mut new_queue_item_required_for_node = true;
for mut q in queue.iter_mut() {
if &q.0 == n {
if q.1 >= astar {
new_queue_item_required_for_node = false;
q.1 = astar;
q.2 = previous_nodes_traversed.clone();
q.3 = complexity;
}
}
}
if new_queue_item_required_for_node {
queue.push((*n, astar, previous_nodes_traversed, complexity));
}
}
} else {
node_astar_scores.insert(*n, astar);
queue.push((*n, astar, previous_nodes_traversed, complexity));
}
}
queue.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
}
let mut best_path = queue[0].2.clone();
best_path.push(end_node);
best_path
}
fn a_star_score(complexity: f32, weighting: f32) -> f32 {
complexity + weighting
}
fn calculate_node_weight(
current_node: &(i32, i32),
end_node: &(i32, i32),
orientation: &HexOrientation,
) -> f32 {
let cubic_start = offset_to_cubic((current_node.0, current_node.1), orientation);
let cubic_end = offset_to_cubic((end_node.0, end_node.1), orientation);
node_distance(cubic_start, cubic_end) as f32
}
#[cfg(test)]
mod tests {
use crate::astar_offset::astar_path;
use crate::astar_offset::calculate_node_weight;
use crate::HexOrientation;
use std::collections::HashMap;
#[test]
fn node_weight_positive() {
let source: (i32, i32) = (2, 2);
let end_node: (i32, i32) = (4, 4);
let orientation = HexOrientation::FlatTopOddUp;
let weight = calculate_node_weight(&source, &end_node, &orientation);
let actual_weight = 3.0;
assert_eq!(actual_weight, weight);
}
#[test]
fn node_weight_negative() {
let source: (i32, i32) = (4, 4);
let end_node: (i32, i32) = (2, 2);
let orientation = HexOrientation::FlatTopOddUp;
let weight = calculate_node_weight(&source, &end_node, &orientation);
let actual_weight = 3.0;
assert_eq!(actual_weight, weight);
}
#[test]
fn node_weight_positive_and_negative() {
let source: (i32, i32) = (2, 4);
let end_node: (i32, i32) = (4, 2);
let orientation = HexOrientation::FlatTopOddUp;
let weight = calculate_node_weight(&source, &end_node, &orientation);
let actual_weight = 3.0;
assert_eq!(actual_weight, weight);
}
#[test]
fn astar_up_right() {
let start_node: (i32, i32) = (0, 0);
let mut nodes: HashMap<(i32, i32), f32> = HashMap::new();
nodes.insert((0, 0), 1.0);
nodes.insert((0, 1), 1.0);
nodes.insert((0, 2), 1.0);
nodes.insert((0, 3), 3.0);
nodes.insert((1, 0), 2.0);
nodes.insert((1, 1), 9.0);
nodes.insert((1, 2), 4.0);
nodes.insert((1, 3), 2.0);
nodes.insert((2, 0), 2.0);
nodes.insert((2, 1), 6.0);
nodes.insert((2, 2), 8.0);
nodes.insert((2, 3), 9.0);
nodes.insert((3, 0), 3.0);
nodes.insert((3, 1), 4.0);
nodes.insert((3, 2), 5.0);
nodes.insert((3, 3), 2.0);
let end_node: (i32, i32) = (3, 3);
let min_column = -1;
let max_column = 4;
let min_row = -1;
let max_row = 4;
let orientation = HexOrientation::FlatTopOddUp;
let best = astar_path(
start_node,
nodes,
end_node,
min_column,
max_column,
min_row,
max_row,
orientation,
);
let actual = vec![(0, 0), (0, 1), (0, 2), (1, 2), (2, 3), (3, 3)];
assert_eq!(actual, best);
}
#[test]
fn astar_right_up() {
let start_node: (i32, i32) = (0, 0);
let mut nodes: HashMap<(i32, i32), f32> = HashMap::new();
nodes.insert((0, 0), 1.0);
nodes.insert((0, 1), 6.0);
nodes.insert((0, 2), 1.0);
nodes.insert((0, 3), 3.0);
nodes.insert((1, 0), 2.0);
nodes.insert((1, 1), 9.0);
nodes.insert((1, 2), 4.0);
nodes.insert((1, 3), 2.0);
nodes.insert((2, 0), 2.0);
nodes.insert((2, 1), 6.0);
nodes.insert((2, 2), 8.0);
nodes.insert((2, 3), 9.0);
nodes.insert((3, 0), 3.0);
nodes.insert((3, 1), 4.0);
nodes.insert((3, 2), 5.0);
nodes.insert((3, 3), 2.0);
let end_node: (i32, i32) = (3, 3);
let min_column = -1;
let max_column = 4;
let min_row = -1;
let max_row = 4;
let orientation = HexOrientation::FlatTopOddUp;
let best = astar_path(
start_node,
nodes,
end_node,
min_column,
max_column,
min_row,
max_row,
orientation,
);
let actual = vec![(0, 0), (1, 0), (2, 0), (3, 0), (3, 1), (3, 2), (3, 3)];
assert_eq!(actual, best);
}
#[test]
fn astar_down_left() {
let start_node: (i32, i32) = (3, 3);
let mut nodes: HashMap<(i32, i32), f32> = HashMap::new();
nodes.insert((0, 0), 1.0);
nodes.insert((0, 1), 1.0);
nodes.insert((0, 2), 1.0);
nodes.insert((0, 3), 3.0);
nodes.insert((1, 0), 2.0);
nodes.insert((1, 1), 9.0);
nodes.insert((1, 2), 4.0);
nodes.insert((1, 3), 2.0);
nodes.insert((2, 0), 2.0);
nodes.insert((2, 1), 6.0);
nodes.insert((2, 2), 8.0);
nodes.insert((2, 3), 9.0);
nodes.insert((3, 0), 3.0);
nodes.insert((3, 1), 4.0);
nodes.insert((3, 2), 5.0);
nodes.insert((3, 3), 2.0);
let end_node: (i32, i32) = (0, 0);
let min_column = -1;
let max_column = 4;
let min_row = -1;
let max_row = 4;
let orientation = HexOrientation::FlatTopOddUp;
let best = astar_path(
start_node,
nodes,
end_node,
min_column,
max_column,
min_row,
max_row,
orientation,
);
let actual = vec![(3, 3), (2, 3), (1, 2), (0, 2), (0, 1), (0, 0)];
assert_eq!(actual, best);
}
#[test]
fn astar_left_down() {
let start_node: (i32, i32) = (3, 3);
let mut nodes: HashMap<(i32, i32), f32> = HashMap::new();
nodes.insert((0, 0), 1.0);
nodes.insert((0, 1), 1.0);
nodes.insert((0, 2), 1.0);
nodes.insert((0, 3), 3.0);
nodes.insert((1, 0), 2.0);
nodes.insert((1, 1), 9.0);
nodes.insert((1, 2), 2.0);
nodes.insert((1, 3), 2.0);
nodes.insert((2, 0), 6.0);
nodes.insert((2, 1), 6.0);
nodes.insert((2, 2), 8.0);
nodes.insert((2, 3), 4.0);
nodes.insert((3, 0), 3.0);
nodes.insert((3, 1), 9.0);
nodes.insert((3, 2), 5.0);
nodes.insert((3, 3), 2.0);
let end_node: (i32, i32) = (0, 0);
let min_column = -1;
let max_column = 4;
let min_row = -1;
let max_row = 4;
let orientation = HexOrientation::FlatTopOddUp;
let best = astar_path(
start_node,
nodes,
end_node,
min_column,
max_column,
min_row,
max_row,
orientation,
);
let actual = vec![(3, 3), (2, 3), (1, 2), (0, 2), (0, 1), (0, 0)];
assert_eq!(actual, best);
}
#[test]
fn astar_odd_column_down() {
let start_node: (i32, i32) = (0, 0);
let mut nodes: HashMap<(i32, i32), f32> = HashMap::new();
nodes.insert((0, 0), 1.0);
nodes.insert((0, 1), 1.0);
nodes.insert((0, 2), 1.0);
nodes.insert((0, 3), 3.0);
nodes.insert((1, 0), 4.0);
nodes.insert((1, 1), 2.0);
nodes.insert((1, 2), 9.0);
nodes.insert((1, 3), 2.0);
nodes.insert((2, 0), 6.0);
nodes.insert((2, 1), 6.0);
nodes.insert((2, 2), 8.0);
nodes.insert((2, 3), 4.0);
nodes.insert((3, 0), 2.0);
nodes.insert((3, 1), 3.0);
nodes.insert((3, 2), 9.0);
nodes.insert((3, 3), 5.0);
let end_node: (i32, i32) = (2, 3);
let min_column = -1;
let max_column = 4;
let min_row = -1;
let max_row = 4;
let orientation = HexOrientation::FlatTopOddDown;
let best = astar_path(
start_node,
nodes,
end_node,
min_column,
max_column,
min_row,
max_row,
orientation,
);
let actual = vec![(0, 0), (0, 1), (0, 2), (1, 3), (2, 3)];
assert_eq!(actual, best);
}
}