diff --git a/crates/map/src/helpers/astar.cairo b/crates/map/src/finders/astar.cairo similarity index 83% rename from crates/map/src/helpers/astar.cairo rename to crates/map/src/finders/astar.cairo index f02669b..6fb0c44 100644 --- a/crates/map/src/helpers/astar.cairo +++ b/crates/map/src/finders/astar.cairo @@ -6,6 +6,7 @@ use core::dict::{Felt252Dict, Felt252DictTrait}; // Internal imports +use origami_map::finders::finder::Finder; use origami_map::helpers::heap::{Heap, HeapTrait}; use origami_map::helpers::bitmap::Bitmap; use origami_map::helpers::seeder::Seeder; @@ -70,7 +71,7 @@ pub impl Astar of AstarTrait { }; // [Return] The path from the start to the target - Self::path(ref heap, start, target) + Finder::path_with_heap(ref heap, start, target) } /// Check if the position can be visited in the specified direction. @@ -123,9 +124,9 @@ pub impl Astar of AstarTrait { fn assess( width: u8, neighbor_position: u8, current: Node, target: Node, ref heap: Heap, ) { - let distance = Self::heuristic(current.position, neighbor_position, width); + let distance = Finder::manhattan(current.position, neighbor_position, width); let neighbor_gcost = current.gcost + distance; - let neighbor_hcost = Self::heuristic(neighbor_position, target.position, width); + let neighbor_hcost = Finder::manhattan(neighbor_position, target.position, width); let mut neighbor = match heap.get(neighbor_position.into()) { Option::Some(node) => node, Option::None => NodeTrait::new( @@ -141,58 +142,6 @@ pub impl Astar of AstarTrait { return heap.update(neighbor); } } - - /// Compute the heuristic cost between two positions. - /// # Arguments - /// * `position` - The current position - /// * `target` - The target position - /// * `width` - The width of the grid - /// # Returns - /// * The heuristic cost between the two positions - #[inline] - fn heuristic(position: u8, target: u8, width: u8) -> u16 { - let (x1, y1) = (position % width, position / width); - let (x2, y2) = (target % width, target / width); - let dx = if x1 > x2 { - x1 - x2 - } else { - x2 - x1 - }; - let dy = if y1 > y2 { - y1 - y2 - } else { - y2 - y1 - }; - (dx + dy).into() - } - - /// Reconstruct the path from the target to the start. - /// # Arguments - /// * `heap` - The heap of nodes - /// * `start` - The starting node - /// * `target` - The target node - /// # Returns - /// * The span of positions from the target to the start - #[inline] - fn path(ref heap: Heap, start: Node, target: Node) -> Span { - // [Check] The heap contains the target - let mut path: Array = array![]; - match heap.get(target.position) { - Option::None => { path.span() }, - Option::Some(mut current) => { - // [Compute] Reconstruct the path from the target to the start - loop { - if current.position == start.position { - break; - } - path.append(current.position); - current = heap.at(current.source); - }; - // [Return] The path from the start to the target - path.span() - }, - } - } } #[cfg(test)] diff --git a/crates/map/src/helpers/bfs.cairo b/crates/map/src/finders/bfs.cairo similarity index 79% rename from crates/map/src/helpers/bfs.cairo rename to crates/map/src/finders/bfs.cairo index df2ec89..67d89bd 100644 --- a/crates/map/src/helpers/bfs.cairo +++ b/crates/map/src/finders/bfs.cairo @@ -4,17 +4,17 @@ use core::dict::{Felt252Dict, Felt252DictTrait}; // Internal imports -use origami_map::helpers::astar::Astar; +use origami_map::finders::finder::Finder; use origami_map::helpers::bitmap::Bitmap; use origami_map::helpers::seeder::Seeder; use origami_map::types::node::{Node, NodeTrait}; use origami_map::types::direction::{Direction, DirectionTrait}; -/// BFS implementation for pathfinding +/// BreadthFirstSearch implementation for pathfinding #[generate_trait] -pub impl BFS of BFSTrait { - /// Searches for a path from 'from' to 'to' on the given grid using BFS +pub impl BreadthFirstSearch of BreadthFirstSearchTrait { + /// Searches for a path from 'from' to 'to' on the given grid using BreadthFirstSearch /// /// # Arguments /// * `grid` - The grid represented as a felt252 @@ -42,7 +42,7 @@ pub impl BFS of BFSTrait { let mut parents: Felt252Dict = Default::default(); visited.insert(start.position.into(), true); - // [Compute] BFS until the target is reached or queue is empty + // [Compute] BreadthFirstSearch until the target is reached or queue is empty let mut path_found = false; while let Option::Some(current) = queue.pop_front() { // [Check] Stop if we reached the target @@ -55,7 +55,7 @@ pub impl BFS of BFSTrait { let mut directions = DirectionTrait::compute_shuffled_directions(seed); while directions != 0 { let direction = DirectionTrait::pop_front(ref directions); - if Astar::check(grid, width, height, current.position, direction, ref visited) { + if Finder::check(grid, width, height, current.position, direction, ref visited) { let neighbor_position = direction.next(current.position, width); parents.insert(neighbor_position.into(), current.position); let neighbor = NodeTrait::new(neighbor_position, current.position, 0, 0); @@ -69,31 +69,14 @@ pub impl BFS of BFSTrait { if !path_found { return array![].span(); }; - Self::path(parents, start, target) - } - - /// Reconstructs the path from start to target using the parents dictionary - #[inline] - fn path(mut parents: Felt252Dict, start: Node, target: Node) -> Span { - let mut path: Array = array![]; - let mut current = target.position; - - loop { - if current == start.position { - break; - } - path.append(current); - current = parents.get(current.into()); - }; - - path.span() + Finder::path_with_parents(ref parents, start, target) } } #[cfg(test)] mod test { // Local imports - use super::BFS; + use super::BreadthFirstSearch; #[test] fn test_bfs_search_small() { @@ -105,7 +88,7 @@ mod test { let height = 3; let from = 0; let to = 8; - let path = BFS::search(grid, width, height, from, to); + let path = BreadthFirstSearch::search(grid, width, height, from, to); assert_eq!(path, array![8, 7, 6, 3].span()); } @@ -119,7 +102,7 @@ mod test { let height = 3; let from = 0; let to = 8; - let path = BFS::search(grid, width, height, from, to); + let path = BreadthFirstSearch::search(grid, width, height, from, to); assert_eq!(path, array![].span()); } @@ -134,7 +117,7 @@ mod test { let height = 4; let from = 0; let to = 14; - let path = BFS::search(grid, width, height, from, to); + let path = BreadthFirstSearch::search(grid, width, height, from, to); assert_eq!(path, array![14, 15, 11, 7, 6, 5, 4].span()); } @@ -148,7 +131,7 @@ mod test { let height = 2; let from = 0; let to = 1; - let path = BFS::search(grid, width, height, from, to); + let path = BreadthFirstSearch::search(grid, width, height, from, to); assert_eq!(path, array![1].span()); } @@ -164,7 +147,7 @@ mod test { let height = 4; let from = 0; let to = 19; - let path = BFS::search(grid, width, height, from, to); + let path = BreadthFirstSearch::search(grid, width, height, from, to); assert_eq!(path, array![19, 18, 13, 12, 11, 6, 1].span()); } @@ -177,7 +160,7 @@ mod test { let height = 1; let from = 0; let to = 5; - let path = BFS::search(grid, width, height, from, to); + let path = BreadthFirstSearch::search(grid, width, height, from, to); assert_eq!(path, array![5, 4, 3, 2, 1].span()); } @@ -192,7 +175,7 @@ mod test { let height = 3; let from = 0; let to = 8; - let path = BFS::search(grid, width, height, from, to); + let path = BreadthFirstSearch::search(grid, width, height, from, to); assert_eq!(path, array![].span()); } } diff --git a/crates/map/src/finders/finder.cairo b/crates/map/src/finders/finder.cairo new file mode 100644 index 0000000..af2227b --- /dev/null +++ b/crates/map/src/finders/finder.cairo @@ -0,0 +1,297 @@ +//! A* algorithm implementation for pathfinding. + +// Core imports + +use core::dict::{Felt252Dict, Felt252DictTrait}; +use core::num::traits::Sqrt; + +// Internal imports + +use origami_map::helpers::heap::{Heap, HeapTrait}; +use origami_map::helpers::bitmap::Bitmap; +use origami_map::types::node::{Node, NodeTrait}; +use origami_map::types::direction::{Direction, DirectionTrait}; + +#[generate_trait] +pub impl Finder of FinderTrait { + /// Check if the position can be visited in the specified direction. + /// # Arguments + /// * `grid` - The grid to search (1 is walkable and 0 is not) + /// * `width` - The width of the grid + /// * `height` - The height of the grid + /// * `position` - The current position + /// * `direction` - The direction to check + /// * `visited` - The visited nodes + /// # Returns + /// * Whether the position can be visited in the specified direction + #[inline] + fn check( + grid: felt252, + width: u8, + height: u8, + position: u8, + direction: Direction, + ref visited: Felt252Dict + ) -> bool { + let (x, y) = (position % width, position / width); + match direction { + Direction::North => (y < height - 1) + && (Bitmap::get(grid, position + width) == 1) + && !visited.get((position + width).into()), + Direction::East => (x > 0) + && (Bitmap::get(grid, position - 1) == 1) + && !visited.get((position - 1).into()), + Direction::South => (y > 0) + && (Bitmap::get(grid, position - width) == 1) + && !visited.get((position - width).into()), + Direction::West => (x < width - 1) + && (Bitmap::get(grid, position + 1) == 1) + && !visited.get((position + 1).into()), + _ => false, + } + } + + /// Compute the amplified euclidean distance between two positions. + /// # Arguments + /// * `position` - The current position + /// * `target` - The target position + /// * `width` - The width of the grid + /// # Returns + /// * The amplified euclidean distance between the two positions + #[inline] + fn euclidean(position: u8, target: u8, width: u8, multiplier: u32) -> u16 { + let (x1, y1) = (position % width, position / width); + let (x2, y2) = (target % width, target / width); + let dx = if x1 > x2 { + x1 - x2 + } else { + x2 - x1 + }; + let dy = if y1 > y2 { + y1 - y2 + } else { + y2 - y1 + }; + (multiplier * (dx.into() * dx.into() + dy.into() * dy.into())).sqrt() + } + + /// Compute the manhattan distance between two positions. + /// # Arguments + /// * `position` - The current position + /// * `target` - The target position + /// * `width` - The width of the grid + /// # Returns + /// * The manhattan distance between the two positions + #[inline] + fn manhattan(position: u8, target: u8, width: u8) -> u16 { + let (x1, y1) = (position % width, position / width); + let (x2, y2) = (target % width, target / width); + let dx = if x1 > x2 { + x1 - x2 + } else { + x2 - x1 + }; + let dy = if y1 > y2 { + y1 - y2 + } else { + y2 - y1 + }; + (dx + dy).into() + } + + /// Reconstructs the path from start to target using the parents dictionary. + /// # Arguments + /// * `parents` - The parents dictionary + /// * `start` - The starting node + /// * `target` - The target node + /// # Returns + /// * The span of positions from the target to the start + #[inline] + fn path_with_parents(ref parents: Felt252Dict, start: Node, target: Node) -> Span { + let mut path: Array = array![]; + let mut current = target.position; + loop { + if current == start.position { + break; + } + path.append(current); + current = parents.get(current.into()); + }; + path.span() + } + + /// Reconstruct the path from the target to the start using a heap. + /// # Arguments + /// * `heap` - The heap of nodes + /// * `start` - The starting node + /// * `target` - The target node + /// # Returns + /// * The span of positions from the target to the start + #[inline] + fn path_with_heap(ref heap: Heap, start: Node, target: Node) -> Span { + // [Check] The heap contains the target + let mut path: Array = array![]; + match heap.get(target.position) { + Option::None => { path.span() }, + Option::Some(mut current) => { + // [Compute] Reconstruct the path from the target to the start + loop { + path.append(current.position); + if current.source == start.position { + break; + } + current = heap.at(current.source); + }; + // [Return] The path from the start to the target + path.span() + }, + } + } +} + +#[cfg(test)] +mod test { + // Local imports + + use super::{Finder, Node, NodeTrait, Felt252Dict, Direction, Heap, HeapTrait}; + + #[test] + fn test_finder_euclidean() { + // x 0 0 + // 0 0 0 + // 0 0 s + let start = 0; + let target = 8; + let width = 3; + assert_eq!(Finder::euclidean(start, target, width, 1), 2); + assert_eq!(Finder::euclidean(start, target, width, 100), 28); + assert_eq!(Finder::euclidean(start, target, width, 10000), 282); + } + + #[test] + fn test_finder_manhattan() { + // x 0 0 + // 0 0 0 + // 0 0 s + let start = 0; + let target = 8; + let width = 3; + assert_eq!(Finder::manhattan(start, target, width), 4); + } + + #[test] + fn test_finder_check_corner() { + // 1 1 1 + // 1 0 1 + // 1 1 x + let grid: felt252 = 0x1EF; + let width = 3; + let height = 3; + let position = 0; + let mut visited: Felt252Dict = Default::default(); + assert_eq!( + Finder::check(grid, width, height, position, Direction::North, ref visited), true + ); + assert_eq!( + Finder::check(grid, width, height, position, Direction::East, ref visited), false + ); + assert_eq!( + Finder::check(grid, width, height, position, Direction::South, ref visited), false + ); + assert_eq!( + Finder::check(grid, width, height, position, Direction::West, ref visited), true + ); + } + + #[test] + fn test_finder_check_edge() { + // 1 1 1 + // 1 0 1 + // 1 x 1 + let grid: felt252 = 0x1EF; + let width = 3; + let height = 3; + let position = 1; + let mut visited: Felt252Dict = Default::default(); + assert_eq!( + Finder::check(grid, width, height, position, Direction::North, ref visited), false + ); + assert_eq!( + Finder::check(grid, width, height, position, Direction::East, ref visited), true + ); + assert_eq!( + Finder::check(grid, width, height, position, Direction::South, ref visited), false + ); + assert_eq!( + Finder::check(grid, width, height, position, Direction::West, ref visited), true + ); + } + + #[test] + fn test_finder_check_inside() { + // 1 1 1 + // 1 x 0 + // 1 0 1 + let grid: felt252 = 0x1F5; + let width = 3; + let height = 3; + let position = 4; + let mut visited: Felt252Dict = Default::default(); + assert_eq!( + Finder::check(grid, width, height, position, Direction::North, ref visited), true + ); + assert_eq!( + Finder::check(grid, width, height, position, Direction::East, ref visited), false + ); + assert_eq!( + Finder::check(grid, width, height, position, Direction::South, ref visited), false + ); + assert_eq!( + Finder::check(grid, width, height, position, Direction::West, ref visited), true + ); + } + + #[test] + fn test_finder_path_with_parents() { + // 1 < 1 < 1 + // ^ + // 1 > 1 > 1 + // ^ + // 1 < 1 < 1 + let start: Node = NodeTrait::new(0, 0, 0, 0); + let target: Node = NodeTrait::new(8, 7, 0, 0); + let mut parents: Felt252Dict = Default::default(); + parents.insert(1, 0); + parents.insert(2, 1); + parents.insert(5, 2); + parents.insert(4, 5); + parents.insert(3, 4); + parents.insert(6, 3); + parents.insert(7, 6); + parents.insert(8, 7); + let path = Finder::path_with_parents(ref parents, start, target); + assert_eq!(path, array![8, 7, 6, 3, 4, 5, 2, 1].span()); + } + + #[test] + fn test_finder_path_with_heap() { + // 1 < 1 < 1 + // ^ + // 1 > 1 > 1 + // ^ + // 1 < 1 < 1 + let start: Node = NodeTrait::new(0, 0, 0, 0); + let target: Node = NodeTrait::new(8, 7, 0, 0); + let mut heap: Heap = HeapTrait::new(); + heap.add(NodeTrait::new(1, 0, 0, 0)); + heap.add(NodeTrait::new(2, 1, 0, 0)); + heap.add(NodeTrait::new(5, 2, 0, 0)); + heap.add(NodeTrait::new(4, 5, 0, 0)); + heap.add(NodeTrait::new(3, 4, 0, 0)); + heap.add(NodeTrait::new(6, 3, 0, 0)); + heap.add(NodeTrait::new(7, 6, 0, 0)); + heap.add(NodeTrait::new(8, 7, 0, 0)); + let path = Finder::path_with_heap(ref heap, start, target); + assert_eq!(path, array![8, 7, 6, 3, 4, 5, 2, 1].span()); + } +} diff --git a/crates/map/src/helpers/greedy.cairo b/crates/map/src/finders/greedy.cairo similarity index 91% rename from crates/map/src/helpers/greedy.cairo rename to crates/map/src/finders/greedy.cairo index da12269..f4b6161 100644 --- a/crates/map/src/helpers/greedy.cairo +++ b/crates/map/src/finders/greedy.cairo @@ -7,7 +7,7 @@ use core::dict::{Felt252Dict, Felt252DictTrait}; // Internal imports use origami_map::helpers::heap::{Heap, HeapTrait}; -use origami_map::helpers::astar::Astar; +use origami_map::finders::finder::Finder; use origami_map::helpers::bitmap::Bitmap; use origami_map::helpers::seeder::Seeder; use origami_map::types::node::{Node, NodeTrait}; @@ -49,29 +49,29 @@ pub impl Greedy of GreedyTrait { let seed = Seeder::shuffle(grid, current.position.into()); let mut directions = DirectionTrait::compute_shuffled_directions(seed); let direction: Direction = DirectionTrait::pop_front(ref directions); - if Astar::check(grid, width, height, current.position, direction, ref visited) { + if Finder::check(grid, width, height, current.position, direction, ref visited) { let neighbor_position = direction.next(current.position, width); Self::assess(width, neighbor_position, current, target, ref heap); } let direction: Direction = DirectionTrait::pop_front(ref directions); - if Astar::check(grid, width, height, current.position, direction, ref visited) { + if Finder::check(grid, width, height, current.position, direction, ref visited) { let neighbor_position = direction.next(current.position, width); Self::assess(width, neighbor_position, current, target, ref heap); } let direction: Direction = DirectionTrait::pop_front(ref directions); - if Astar::check(grid, width, height, current.position, direction, ref visited) { + if Finder::check(grid, width, height, current.position, direction, ref visited) { let neighbor_position = direction.next(current.position, width); Self::assess(width, neighbor_position, current, target, ref heap); } let direction: Direction = DirectionTrait::pop_front(ref directions); - if Astar::check(grid, width, height, current.position, direction, ref visited) { + if Finder::check(grid, width, height, current.position, direction, ref visited) { let neighbor_position = direction.next(current.position, width); Self::assess(width, neighbor_position, current, target, ref heap); } }; // [Return] The path from the start to the target - Astar::path(ref heap, start, target) + Finder::path_with_heap(ref heap, start, target) } /// Assess the neighbor node and update the heap. @@ -87,7 +87,7 @@ pub impl Greedy of GreedyTrait { fn assess( width: u8, neighbor_position: u8, current: Node, target: Node, ref heap: Heap, ) { - let neighbor_hcost = Astar::heuristic(neighbor_position, target.position, width); + let neighbor_hcost = Finder::manhattan(neighbor_position, target.position, width); let mut neighbor = match heap.get(neighbor_position.into()) { Option::Some(node) => node, Option::None => NodeTrait::new(neighbor_position, current.position, 0, neighbor_hcost), diff --git a/crates/map/src/helpers/caver.cairo b/crates/map/src/generators/caver.cairo similarity index 100% rename from crates/map/src/helpers/caver.cairo rename to crates/map/src/generators/caver.cairo diff --git a/crates/map/src/helpers/digger.cairo b/crates/map/src/generators/digger.cairo similarity index 99% rename from crates/map/src/helpers/digger.cairo rename to crates/map/src/generators/digger.cairo index 966dce8..381d789 100644 --- a/crates/map/src/helpers/digger.cairo +++ b/crates/map/src/generators/digger.cairo @@ -3,10 +3,10 @@ // Internal imports use origami_map::helpers::bitmap::Bitmap; -use origami_map::helpers::caver::Caver; use origami_map::helpers::seeder::Seeder; -use origami_map::helpers::mazer::Mazer; use origami_map::helpers::asserter::Asserter; +use origami_map::generators::caver::Caver; +use origami_map::generators::mazer::Mazer; use origami_map::types::direction::{Direction, DirectionTrait}; /// Implementation of the `DiggerTrait` trait. diff --git a/crates/map/src/helpers/mazer.cairo b/crates/map/src/generators/mazer.cairo similarity index 100% rename from crates/map/src/helpers/mazer.cairo rename to crates/map/src/generators/mazer.cairo diff --git a/crates/map/src/helpers/spreader.cairo b/crates/map/src/generators/spreader.cairo similarity index 100% rename from crates/map/src/helpers/spreader.cairo rename to crates/map/src/generators/spreader.cairo diff --git a/crates/map/src/helpers/walker.cairo b/crates/map/src/generators/walker.cairo similarity index 99% rename from crates/map/src/helpers/walker.cairo rename to crates/map/src/generators/walker.cairo index 3051355..0606753 100644 --- a/crates/map/src/helpers/walker.cairo +++ b/crates/map/src/generators/walker.cairo @@ -4,8 +4,8 @@ use origami_map::helpers::bitmap::Bitmap; use origami_map::helpers::seeder::Seeder; -use origami_map::helpers::mazer::Mazer; use origami_map::helpers::asserter::Asserter; +use origami_map::generators::mazer::Mazer; use origami_map::types::direction::{Direction, DirectionTrait}; // Constants diff --git a/crates/map/src/lib.cairo b/crates/map/src/lib.cairo index 4d09365..da07c59 100644 --- a/crates/map/src/lib.cairo +++ b/crates/map/src/lib.cairo @@ -6,20 +6,27 @@ pub mod types { pub mod node; } -pub mod helpers { - pub mod asserter; - pub mod bitmap; - pub mod power; - pub mod seeder; +pub mod finders { + pub mod finder; + pub mod astar; + pub mod bfs; + pub mod greedy; +} + +pub mod generators { pub mod digger; pub mod mazer; pub mod caver; pub mod walker; pub mod spreader; - pub mod astar; +} + +pub mod helpers { + pub mod asserter; + pub mod bitmap; + pub mod power; + pub mod seeder; pub mod heap; - pub mod greedy; - pub mod bfs; #[cfg(target: "test")] pub mod printer; diff --git a/crates/map/src/map.cairo b/crates/map/src/map.cairo index c4fe78d..50d9a59 100644 --- a/crates/map/src/map.cairo +++ b/crates/map/src/map.cairo @@ -3,13 +3,13 @@ // Internal imports use origami_map::helpers::power::TwoPower; -use origami_map::helpers::mazer::Mazer; use origami_map::helpers::asserter::Asserter; -use origami_map::helpers::walker::Walker; -use origami_map::helpers::caver::Caver; -use origami_map::helpers::digger::Digger; -use origami_map::helpers::spreader::Spreader; -use origami_map::helpers::astar::Astar; +use origami_map::generators::mazer::Mazer; +use origami_map::generators::walker::Walker; +use origami_map::generators::caver::Caver; +use origami_map::generators::digger::Digger; +use origami_map::generators::spreader::Spreader; +use origami_map::finders::astar::Astar; /// Types. #[derive(Copy, Drop)]