use core::ops::Add;
use core::cmp::{max, min};
use heapless::{
Vec,
binary_heap::{BinaryHeap, Max},
spsc::Queue,
consts::*
};
use map_vec::Map;
use num_rational::BigRational;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct Point2D {
pub x: i32,
pub y: i32,
}
impl Default for Point2D {
fn default() -> Self {
Self {
x: 0,
y: 0,
}
}
}
impl Add for Point2D {
type Output = Self;
fn add(self, other: Self) -> Self {
Self {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
pub trait Grid2D {
fn dimensions(&self) -> Point2D;
fn lower_bound(&self) -> Point2D {
Default::default()
}
fn upper_bound(&self) -> Point2D {
let dim = self.dimensions();
let low = self.lower_bound();
Point2D {
x: low.x + dim.x,
y: low.y + dim.y,
}
}
fn in_bounds(&self, point: Point2D) -> bool {
let low = self.lower_bound();
let upp = self.upper_bound();
low.x <= point.x && point.x < upp.x &&
low.y <= point.y && point.y < upp.y
}
fn point2d_to_index(&self, point: Point2D) -> usize {
let dim = self.dimensions();
(point.y * dim.x + point.x) as usize
}
fn index_to_point2d(&self, index: usize) -> Point2D {
let dim = self.dimensions();
let x = index as i32 % dim.x;
let y = index as i32 / dim.x;
Point2D {
x,
y,
}
}
#[allow(unused_variables)]
fn is_opaque(&self, point: Point2D) -> bool {
true
}
fn get_possible_neighbors(&self, point: Point2D) -> [Point2D; 8] {
[
point + Point2D {
x: 0,
y: 1,
},
point + Point2D {
x: 1,
y: 0,
},
point + Point2D {
x: 0,
y: -1,
},
point + Point2D {
x: -1,
y: 0,
},
point + Point2D {
x: 1,
y: 1,
},
point + Point2D {
x: 1,
y: -1,
},
point + Point2D {
x: -1,
y: -1,
},
point + Point2D {
x: -1,
y: 1,
},
]
}
fn is_possible_neighbor(&self, p1: Point2D, p2: Point2D) -> bool {
self.get_possible_neighbors(p1)
.iter()
.any(|&x| x == p2)
}
fn get_neighbors(&self, point: Point2D) -> [Option<Point2D>; 8] {
let mut arr: [Option<Point2D>; 8] = [None; 8];
let possible_neighbors = self.get_possible_neighbors(point);
for (i, n) in possible_neighbors.iter().enumerate() {
if !self.is_opaque(*n) && self.in_bounds(*n) {
arr[i] = Some(*n);
}
}
arr
}
fn is_neighbor(&self, p1: Point2D, p2: Point2D) -> bool {
self.get_neighbors(p1)
.iter()
.any(|&x| x == Some(p2))
}
fn get_neighbors_with_cost(&self, point: Point2D) -> [(Option<Point2D>, BigRational); 8] {
let mut arr: [(Option<Point2D>, BigRational); 8] = [
(None, BigRational::from_float(0.0).unwrap()),
(None, BigRational::from_float(0.0).unwrap()),
(None, BigRational::from_float(0.0).unwrap()),
(None, BigRational::from_float(0.0).unwrap()),
(None, BigRational::from_float(0.0).unwrap()),
(None, BigRational::from_float(0.0).unwrap()),
(None, BigRational::from_float(0.0).unwrap()),
(None, BigRational::from_float(0.0).unwrap()),
];
let neighbors = self.get_neighbors(point);
for (i, n) in neighbors.iter().enumerate() {
let one = BigRational::from_float(1.0).unwrap();
if n != &None {
arr[i] = (*n, one);
}
}
arr
}
}
pub enum Distance2D {
Pythagoras,
Manhattan,
Chebyshev,
}
impl Distance2D {
pub fn distance(&self, start: Point2D, end: Point2D) -> f64 {
use Distance2D::*;
match self {
Pythagoras => {
let dx = (max(start.x, end.x) - min(start.x, end.x)) as f64;
let dy = (max(start.y, end.y) - min(start.y, end.y)) as f64;
libm::sqrt((dx * dx) + (dy * dy))
}
Manhattan => {
let dx = (max(start.x, end.x) - min(start.x, end.x)) as f64;
let dy = (max(start.y, end.y) - min(start.y, end.y)) as f64;
dx + dy
}
Chebyshev => {
let dx = (max(start.x, end.x) - min(start.x, end.x)) as f64;
let dy = (max(start.y, end.y) - min(start.y, end.y)) as f64;
if dx > dy {
(dx - dy) + 1.0 * dy
} else {
(dy - dx) + 1.0 * dx
}
}
}
}
}
pub fn breadth_first_search(graph: &dyn Grid2D, start: Point2D, goal: Point2D) -> Map<Point2D, Option<Point2D>> {
let mut frontier: Queue<Point2D, U16> = Queue::new();
frontier.enqueue(start).expect("Failed to enqueue");
let mut came_from = Map::<Point2D, Option<Point2D>>::new();
came_from.insert(start, None);
while !frontier.is_empty() {
let current = frontier.dequeue().unwrap();
if current == goal { break }
for next in &graph.get_neighbors(current) {
let next = next.unwrap();
if !came_from.contains_key(&next) {
frontier.enqueue(next).expect("Failed to enqueue");
came_from.insert(next, Some(current));
}
}
}
came_from
}
pub fn dijkstra_search(
graph: &dyn Grid2D,
start: Point2D,
goal: Point2D
) -> (
Map<Point2D, Option<Point2D>>,
Map<Point2D, BigRational>,
) {
let mut frontier: BinaryHeap<(BigRational, Point2D), U16, Max> = BinaryHeap::new();
let zero1 = BigRational::from_float(0.0).unwrap();
let zero2 = BigRational::from_float(0.0).unwrap();
frontier.push((zero1, start)).expect("fail to push to heap");
let mut came_from = Map::<Point2D, Option<Point2D>>::new();
let mut cost_so_far = Map::<Point2D, BigRational>::new();
came_from.insert(start, None);
cost_so_far.insert(start, zero2);
while !frontier.is_empty() {
let current = frontier.pop().unwrap();
let point = current.1;
if current.1 == goal { break }
for (next, cost) in &graph.get_neighbors_with_cost(point) {
let next = next.unwrap();
let new_cost1 = cost_so_far.get(&point).unwrap() + cost;
let new_cost2 = cost_so_far.get(&point).unwrap() + cost;
if !cost_so_far.contains_key(&next) || new_cost1 < *cost_so_far.get(&next).unwrap() {
cost_so_far.insert(next, new_cost1);
let priority = new_cost2;
frontier.push((priority, next)).expect("fail to push to heap");
came_from.insert(next, Some(point));
}
}
}
(came_from, cost_so_far)
}
pub fn a_star_search(
graph: &dyn Grid2D,
start: Point2D,
goal: Point2D
) -> (
Map<Point2D, Option<Point2D>>,
Map<Point2D, BigRational>,
) {
use num_bigint::ToBigInt;
let mut frontier: BinaryHeap<(BigRational, Point2D), U16, Max> = BinaryHeap::new();
let zero1 = BigRational::from_float(0.0).unwrap();
let zero2 = BigRational::from_float(0.0).unwrap();
frontier.push((zero1, start)).expect("fail to push to heap");
let mut came_from = Map::<Point2D, Option<Point2D>>::new();
let mut cost_so_far = Map::<Point2D, BigRational>::new();
came_from.insert(start, None);
cost_so_far.insert(start, zero2);
while !frontier.is_empty() {
let current = frontier.pop().unwrap();
let point = current.1;
if current.1 == goal { break }
for (next, cost) in &graph.get_neighbors_with_cost(point) {
let next = next.unwrap();
let new_cost1 = cost_so_far.get(&point).unwrap() + cost;
let new_cost2 = cost_so_far.get(&point).unwrap() + cost;
if !cost_so_far.contains_key(&next) || new_cost1 < *cost_so_far.get(&next).unwrap() {
cost_so_far.insert(next, new_cost1);
let h = BigRational::from_integer(
heuristic(goal, next).to_bigint().unwrap()
);
let priority = new_cost2 + h;
frontier.push((priority, next)).expect("fail to push to heap");
came_from.insert(next, Some(point));
}
}
}
(came_from, cost_so_far)
}
fn heuristic(p1: Point2D, p2: Point2D) -> i32 {
(p1.x - p2.x).abs() + (p1.y - p2.y).abs()
}
pub fn reconstruct_path(
came_from: Map<Point2D, Option<Point2D>>,
start: Point2D,
goal: Point2D
) -> Vec<Point2D, U16> {
let mut current = goal;
let mut path = Vec::<Point2D, U16>::new();
while current != start {
path.push(current).expect("Cannot push to vector");
current = came_from.get(¤t).unwrap().unwrap();
}
path.push(start).expect("Cannot push to vector");
#[cfg(feature = "reverse_path")]
let path = path.iter()
.cloned()
.rev()
.collect::<Vec<_, U16>>();
path
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}