use crate::{Grid, Path, Point};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Direction {
Left,
Right,
Up,
Down,
}
impl Direction {
pub(crate) const ALL: [Self; 4] = [Self::Left, Self::Right, Self::Up, Self::Down];
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum PointKind {
StraightCorridor,
Branch,
ElbowTurn,
DeadEnd,
}
pub(crate) fn step(
grid: &Grid,
point: Point,
index: usize,
direction: Direction,
) -> Option<(Point, usize)> {
match direction {
Direction::Left if point.x > 0 => {
let next = Point::new(point.x - 1, point.y);
grid.is_walkable(next).then_some((next, index - 1))
}
Direction::Right if point.x + 1 < grid.width() => {
let next = Point::new(point.x + 1, point.y);
grid.is_walkable(next).then_some((next, index + 1))
}
Direction::Up if point.y > 0 => {
let next = Point::new(point.x, point.y - 1);
grid.is_walkable(next)
.then_some((next, index - grid.width()))
}
Direction::Down if point.y + 1 < grid.height() => {
let next = Point::new(point.x, point.y + 1);
grid.is_walkable(next)
.then_some((next, index + grid.width()))
}
_ => None,
}
}
pub(crate) fn classify_point_kind(grid: &Grid, point: Point) -> PointKind {
let neighbors = walkable_neighbor_mask(grid, point);
let degree = neighbors.iter().filter(|walkable| **walkable).count();
if degree != 2 {
return if degree <= 1 {
PointKind::DeadEnd
} else {
PointKind::Branch
};
}
if neighbors[0] && neighbors[1] || neighbors[2] && neighbors[3] {
PointKind::StraightCorridor
} else {
PointKind::ElbowTurn
}
}
pub(crate) fn walkable_neighbor_mask(grid: &Grid, point: Point) -> [bool; 4] {
[
point.x > 0 && grid.is_walkable(Point::new(point.x - 1, point.y)),
point.x + 1 < grid.width() && grid.is_walkable(Point::new(point.x + 1, point.y)),
point.y > 0 && grid.is_walkable(Point::new(point.x, point.y - 1)),
point.y + 1 < grid.height() && grid.is_walkable(Point::new(point.x, point.y + 1)),
]
}
pub(crate) fn manhattan_distance(from: Point, to: Point) -> usize {
from.x.abs_diff(to.x) + from.y.abs_diff(to.y)
}
pub(crate) fn straight_segment(from: Point, to: Point) -> Vec<Point> {
let mut points = Vec::with_capacity(manhattan_distance(from, to));
let mut current = from;
while current.x != to.x {
current = if current.x < to.x {
Point::new(current.x + 1, current.y)
} else {
Point::new(current.x - 1, current.y)
};
points.push(current);
}
while current.y != to.y {
current = if current.y < to.y {
Point::new(current.x, current.y + 1)
} else {
Point::new(current.x, current.y - 1)
};
points.push(current);
}
points
}
pub(crate) fn reconstruct_jump_path(
grid: &Grid,
parents: &[Option<usize>],
start_index: usize,
goal_index: usize,
goal_cost: usize,
) -> Path {
let mut segments = Vec::new();
let mut current_index = goal_index;
while let Some(parent_index) = parents[current_index] {
let parent = grid.point_from_index(parent_index);
let current = grid.point_from_index(current_index);
segments.push(straight_segment(parent, current));
current_index = parent_index;
}
let mut steps = vec![grid.point_from_index(start_index)];
for segment in segments.iter().rev() {
steps.extend(segment.iter().copied());
}
Path::from_steps_with_cost(steps, goal_cost).expect("path contains at least one point")
}