From bdb93f3125ebc62cc568834ca69990b2b1d9b37e Mon Sep 17 00:00:00 2001 From: guha-rahul <69rahul16@gmail.com> Date: Wed, 9 Oct 2024 22:27:21 +0530 Subject: [PATCH 1/5] feat/dfs --- crates/map/src/finders/dfs.cairo | 179 +++++++++++++++++++++++++++++++ crates/map/src/lib.cairo | 1 + 2 files changed, 180 insertions(+) create mode 100644 crates/map/src/finders/dfs.cairo diff --git a/crates/map/src/finders/dfs.cairo b/crates/map/src/finders/dfs.cairo new file mode 100644 index 0000000..0f957a3 --- /dev/null +++ b/crates/map/src/finders/dfs.cairo @@ -0,0 +1,179 @@ +//! Depth-First Search algorithm implementation for pathfinding. + +// Core imports +use core::dict::{Felt252Dict, Felt252DictTrait}; + +// Internal imports +use origami_map::finders::astar::Astar; +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}; + +/// DFS implementation for pathfinding +#[generate_trait] +pub impl DFS of DFSTrait { + /// Searches for a path from 'from' to 'to' on the given grid using DFS + /// + /// # Arguments + /// * `grid` - The grid represented as a felt252 + /// * `width` - The width of the grid + /// * `height` - The height of the grid + /// * `from` - The starting position + /// * `to` - The target position + /// + /// # Returns + /// A Span representing the path from 'from' to 'to', or an empty span if no path exists + #[inline] + fn search(grid: felt252, width: u8, height: u8, from: u8, to: u8) -> Span { + // [Check] The start and target are walkable + if Bitmap::get(grid, from) == 0 || Bitmap::get(grid, to) == 0 { + return array![].span(); + } + + // [Effect] Initialize the start and target nodes + let mut start = NodeTrait::new(from, 0, 0, 0); + let target = NodeTrait::new(to, 0, 0, 0); + + // [Effect] Initialize the stack and the visited nodes + let mut stack: Array = array![start]; + let mut visited: Felt252Dict = Default::default(); + let mut parents: Felt252Dict = Default::default(); + + // [Compute] DFS until the target is reached or stack is empty + let mut path_found = false; + while let Option::Some(current) = stack.pop_front() { + // [Check] Stop if we reached the target + if current.position == target.position { + path_found = true; + break; + } + + // [Check] Skip if already visited + if visited.get(current.position.into()) { + continue; + } + + // [Effect] Mark as visited + visited.insert(current.position.into(), true); + + // [Compute] Evaluate the neighbors for all 4 directions + let seed = Seeder::shuffle(grid, current.position.into()); + 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) { + 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); + stack.append(neighbor); + } + }; + }; + + // Reconstruct and return the path if found + 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() + } +} +#[cfg(test)] +mod test { + // Local imports + use super::DFS; + + #[test] + fn test_dfs_search_small() { + // x───┐ + // 1 0 │ + // 0 1 s + let grid: felt252 = 0x1EB; + let width = 3; + let height = 3; + let from = 0; + let to = 8; + let path = DFS::search(grid, width, height, from, to); + assert_eq!(path, array![8, 7, 6, 3].span()); + } + + #[test] + fn test_dfs_search_impossible() { + // x 1 0 + // 1 0 1 + // 0 1 s + let grid: felt252 = 0x1AB; + let width = 3; + let height = 3; + let from = 0; + let to = 8; + let path = DFS::search(grid, width, height, from, to); + assert_eq!(path, array![].span()); + } + + #[test] + fn test_dfs_search_medium() { + // ┌─x 0 0 + // │ 0 1 1 + // └─────┐ + // 1 1 1 s + let grid: felt252 = 0xCBFF; + let width = 4; + let height = 4; + let from = 0; + let to = 14; + let path = DFS::search(grid, width, height, from, to); + assert!( + path.len() > 0 + ); // DFS may not find the shortest path, so we just check if a path is found + } + + #[test] + fn test_dfs_single_cell_path() { + // Grid representation: + // x s + // 1 1 + let grid: felt252 = 0xF; + let width = 2; + let height = 2; + let from = 0; + let to = 1; + let path = DFS::search(grid, width, height, from, to); + assert_eq!(path, array![1].span()); + } + + #[test] + fn test_dfs_maze() { + // Grid representation: + // x 1 0 0 0 + // 0 1 1 1 0 + // 0 0 0 1 0 + // 1 1 1 1 s + let grid: felt252 = 0xC385F; + let width = 5; + let height = 4; + let from = 0; + let to = 19; + let path = DFS::search(grid, width, height, from, to); + assert!( + path.len() > 0 + ); // DFS may not find the shortest path, so we just check if a path is found + } +} diff --git a/crates/map/src/lib.cairo b/crates/map/src/lib.cairo index da07c59..a69bfe2 100644 --- a/crates/map/src/lib.cairo +++ b/crates/map/src/lib.cairo @@ -11,6 +11,7 @@ pub mod finders { pub mod astar; pub mod bfs; pub mod greedy; + pub mod dfs; } pub mod generators { From ff968e818b8e925d1d2535cd1752642eb084df9e Mon Sep 17 00:00:00 2001 From: guha-rahul <69rahul16@gmail.com> Date: Thu, 10 Oct 2024 22:55:39 +0530 Subject: [PATCH 2/5] fix --- crates/map/src/finders/dfs.cairo | 151 ++++++++++++++++--------------- 1 file changed, 78 insertions(+), 73 deletions(-) diff --git a/crates/map/src/finders/dfs.cairo b/crates/map/src/finders/dfs.cairo index 0f957a3..8b67d2e 100644 --- a/crates/map/src/finders/dfs.cairo +++ b/crates/map/src/finders/dfs.cairo @@ -4,16 +4,16 @@ use core::dict::{Felt252Dict, Felt252DictTrait}; // Internal imports -use origami_map::finders::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}; -/// DFS implementation for pathfinding +/// DepthFirstSearch implementation for pathfinding #[generate_trait] -pub impl DFS of DFSTrait { - /// Searches for a path from 'from' to 'to' on the given grid using DFS +pub impl DepthFirstSearch of DepthFirstSearchTrait { + /// Searches for a path from 'from' to 'to' on the given grid using DepthFirstSearch /// /// # Arguments /// * `grid` - The grid represented as a felt252 @@ -32,91 +32,100 @@ pub impl DFS of DFSTrait { } // [Effect] Initialize the start and target nodes - let mut start = NodeTrait::new(from, 0, 0, 0); + let start = NodeTrait::new(from, 0, 0, 0); let target = NodeTrait::new(to, 0, 0, 0); - // [Effect] Initialize the stack and the visited nodes - let mut stack: Array = array![start]; + // [Effect] Initialize visited nodes and parents let mut visited: Felt252Dict = Default::default(); let mut parents: Felt252Dict = Default::default(); - // [Compute] DFS until the target is reached or stack is empty - let mut path_found = false; - while let Option::Some(current) = stack.pop_front() { - // [Check] Stop if we reached the target - if current.position == target.position { - path_found = true; - break; - } - - // [Check] Skip if already visited - if visited.get(current.position.into()) { - continue; - } - - // [Effect] Mark as visited - visited.insert(current.position.into(), true); - - // [Compute] Evaluate the neighbors for all 4 directions - let seed = Seeder::shuffle(grid, current.position.into()); - 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) { - 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); - stack.append(neighbor); - } - }; - }; + // [Compute] Start the recursive DFS + let found = Self::dfs_recursive( + grid, width, height, start, target, ref visited, ref parents + ); // Reconstruct and return the path if found - if !path_found { - return array![].span(); - }; - Self::path(parents, start, target) + if found { + Finder::path_with_parents(ref parents, start, target) + } else { + array![].span() + } } - /// Reconstructs the path from start to target using the parents dictionary + /// Recursive helper function for DFS #[inline] - fn path(mut parents: Felt252Dict, start: Node, target: Node) -> Span { - let mut path: Array = array![]; - let mut current = target.position; + fn dfs_recursive( + grid: felt252, + width: u8, + height: u8, + current: Node, + target: Node, + ref visited: Felt252Dict, + ref parents: Felt252Dict + ) -> bool { + // [Check] Mark current node as visited + visited.insert(current.position.into(), true); + + // [Check] If we've reached the target, we're done + if current.position == target.position { + return true; + } + + // [Compute] Evaluate the neighbors for all 4 directions + let seed = Seeder::shuffle(grid, current.position.into()); + let mut directions = DirectionTrait::compute_shuffled_directions(seed); + let mut found = false; loop { - if current == start.position { + if directions == 0 { break; } - path.append(current); - current = parents.get(current.into()); + let direction = DirectionTrait::pop_front(ref directions); + if Finder::check(grid, width, height, current.position, direction, ref visited) { + let neighbor_position = direction.next(current.position, width); + let neighbor = NodeTrait::new(neighbor_position, current.position, 0, 0); + + // [Effect] Set parent for the neighbor + parents.insert(neighbor_position.into(), current.position); + + // [Recurse] Continue DFS from the neighbor + found = + Self::dfs_recursive( + grid, width, height, neighbor, target, ref visited, ref parents + ); + + if found { + break; + } + } }; - path.span() + // [Check] Return whether we've found the target + found } } + #[cfg(test)] mod test { - // Local imports - use super::DFS; + use super::DepthFirstSearch; #[test] fn test_dfs_search_small() { - // x───┐ - // 1 0 │ - // 0 1 s + // x───┐ 111 8 7 6 + // 1 0 │ 101 5 4 3 + // 0 1 s 011 2 1 0 let grid: felt252 = 0x1EB; let width = 3; let height = 3; let from = 0; let to = 8; - let path = DFS::search(grid, width, height, from, to); + let path = DepthFirstSearch::search(grid, width, height, from, to); assert_eq!(path, array![8, 7, 6, 3].span()); } #[test] fn test_dfs_search_impossible() { - // x 1 0 + // x 1 0 // 1 0 1 // 0 1 s let grid: felt252 = 0x1AB; @@ -124,25 +133,23 @@ mod test { let height = 3; let from = 0; let to = 8; - let path = DFS::search(grid, width, height, from, to); + let path = DepthFirstSearch::search(grid, width, height, from, to); assert_eq!(path, array![].span()); } #[test] fn test_dfs_search_medium() { - // ┌─x 0 0 - // │ 0 1 1 - // └─────┐ - // 1 1 1 s + // 1 x 0 0 15 14 13 12 + // 1 0 1 1 11 10 9 8 + // 1 1 1 1 7 6 5 4 + // 1 1 1 s 3 2 1 0 let grid: felt252 = 0xCBFF; let width = 4; let height = 4; let from = 0; let to = 14; - let path = DFS::search(grid, width, height, from, to); - assert!( - path.len() > 0 - ); // DFS may not find the shortest path, so we just check if a path is found + let path = DepthFirstSearch::search(grid, width, height, from, to); + assert_eq!(path, array![14, 15, 11, 7, 3, 2, 1, 5, 9, 8, 4].span()); } #[test] @@ -153,10 +160,10 @@ mod test { let grid: felt252 = 0xF; let width = 2; let height = 2; - let from = 0; - let to = 1; - let path = DFS::search(grid, width, height, from, to); - assert_eq!(path, array![1].span()); + let from = 3; + let to = 2; + let path = DepthFirstSearch::search(grid, width, height, from, to); + assert_eq!(path, array![2].span()); } #[test] @@ -171,9 +178,7 @@ mod test { let height = 4; let from = 0; let to = 19; - let path = DFS::search(grid, width, height, from, to); - assert!( - path.len() > 0 - ); // DFS may not find the shortest path, so we just check if a path is found + let path = DepthFirstSearch::search(grid, width, height, from, to); + assert_eq!(path, array![19, 18, 13, 12, 11, 6, 1].span()); } } From 4aee9e7e012ac7f97bac58db91997545981c2c24 Mon Sep 17 00:00:00 2001 From: guha-rahul <69rahul16@gmail.com> Date: Thu, 10 Oct 2024 23:00:55 +0530 Subject: [PATCH 3/5] fmt --- crates/map/src/finders/dfs.cairo | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/map/src/finders/dfs.cairo b/crates/map/src/finders/dfs.cairo index 8b67d2e..21368c8 100644 --- a/crates/map/src/finders/dfs.cairo +++ b/crates/map/src/finders/dfs.cairo @@ -12,7 +12,7 @@ use origami_map::types::direction::{Direction, DirectionTrait}; /// DepthFirstSearch implementation for pathfinding #[generate_trait] -pub impl DepthFirstSearch of DepthFirstSearchTrait { +pub impl DFS of DFSTrait { /// Searches for a path from 'from' to 'to' on the given grid using DepthFirstSearch /// /// # Arguments @@ -107,25 +107,25 @@ pub impl DepthFirstSearch of DepthFirstSearchTrait { #[cfg(test)] mod test { - use super::DepthFirstSearch; + use super::DFS; #[test] fn test_dfs_search_small() { - // x───┐ 111 8 7 6 + // x───┐ 111 8 7 6 // 1 0 │ 101 5 4 3 - // 0 1 s 011 2 1 0 + // 0 1 s 011 2 1 0 let grid: felt252 = 0x1EB; let width = 3; let height = 3; let from = 0; let to = 8; - let path = DepthFirstSearch::search(grid, width, height, from, to); + let path = DFS::search(grid, width, height, from, to); assert_eq!(path, array![8, 7, 6, 3].span()); } #[test] fn test_dfs_search_impossible() { - // x 1 0 + // x 1 0 // 1 0 1 // 0 1 s let grid: felt252 = 0x1AB; @@ -133,22 +133,22 @@ mod test { let height = 3; let from = 0; let to = 8; - let path = DepthFirstSearch::search(grid, width, height, from, to); + let path = DFS::search(grid, width, height, from, to); assert_eq!(path, array![].span()); } #[test] fn test_dfs_search_medium() { - // 1 x 0 0 15 14 13 12 - // 1 0 1 1 11 10 9 8 + // 1 x 0 0 15 14 13 12 + // 1 0 1 1 11 10 9 8 // 1 1 1 1 7 6 5 4 - // 1 1 1 s 3 2 1 0 + // 1 1 1 s 3 2 1 0 let grid: felt252 = 0xCBFF; let width = 4; let height = 4; let from = 0; let to = 14; - let path = DepthFirstSearch::search(grid, width, height, from, to); + let path = DFS::search(grid, width, height, from, to); assert_eq!(path, array![14, 15, 11, 7, 3, 2, 1, 5, 9, 8, 4].span()); } @@ -162,7 +162,7 @@ mod test { let height = 2; let from = 3; let to = 2; - let path = DepthFirstSearch::search(grid, width, height, from, to); + let path = DFS::search(grid, width, height, from, to); assert_eq!(path, array![2].span()); } @@ -178,7 +178,7 @@ mod test { let height = 4; let from = 0; let to = 19; - let path = DepthFirstSearch::search(grid, width, height, from, to); + let path = DFS::search(grid, width, height, from, to); assert_eq!(path, array![19, 18, 13, 12, 11, 6, 1].span()); } } From 3c6c26896cbd6beb7e09a35ea1b6ce9044ce0d99 Mon Sep 17 00:00:00 2001 From: guha-rahul <69rahul16@gmail.com> Date: Fri, 11 Oct 2024 22:30:33 +0530 Subject: [PATCH 4/5] fix --- crates/map/src/finders/dfs.cairo | 44 ++++++++++++++++---------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/crates/map/src/finders/dfs.cairo b/crates/map/src/finders/dfs.cairo index 21368c8..019d450 100644 --- a/crates/map/src/finders/dfs.cairo +++ b/crates/map/src/finders/dfs.cairo @@ -12,7 +12,7 @@ use origami_map::types::direction::{Direction, DirectionTrait}; /// DepthFirstSearch implementation for pathfinding #[generate_trait] -pub impl DFS of DFSTrait { +pub impl DepthFirstSearch of DepthFirstSearchTrait { /// Searches for a path from 'from' to 'to' on the given grid using DepthFirstSearch /// /// # Arguments @@ -40,9 +40,7 @@ pub impl DFS of DFSTrait { let mut parents: Felt252Dict = Default::default(); // [Compute] Start the recursive DFS - let found = Self::dfs_recursive( - grid, width, height, start, target, ref visited, ref parents - ); + let found = Self::iter(grid, width, height, start, target, ref visited, ref parents); // Reconstruct and return the path if found if found { @@ -54,7 +52,7 @@ pub impl DFS of DFSTrait { /// Recursive helper function for DFS #[inline] - fn dfs_recursive( + fn iter( grid: felt252, width: u8, height: u8, @@ -63,6 +61,11 @@ pub impl DFS of DFSTrait { ref visited: Felt252Dict, ref parents: Felt252Dict ) -> bool { + // [Check] If the current node has already been visited, return false + if visited.get(current.position.into()) { + return false; + } + // [Check] Mark current node as visited visited.insert(current.position.into(), true); @@ -89,10 +92,7 @@ pub impl DFS of DFSTrait { parents.insert(neighbor_position.into(), current.position); // [Recurse] Continue DFS from the neighbor - found = - Self::dfs_recursive( - grid, width, height, neighbor, target, ref visited, ref parents - ); + found = Self::iter(grid, width, height, neighbor, target, ref visited, ref parents); if found { break; @@ -107,19 +107,19 @@ pub impl DFS of DFSTrait { #[cfg(test)] mod test { - use super::DFS; + use super::DepthFirstSearch; #[test] fn test_dfs_search_small() { - // x───┐ 111 8 7 6 - // 1 0 │ 101 5 4 3 - // 0 1 s 011 2 1 0 + // x * * + // 1 0 * + // 0 1 s let grid: felt252 = 0x1EB; let width = 3; let height = 3; let from = 0; let to = 8; - let path = DFS::search(grid, width, height, from, to); + let path = DepthFirstSearch::search(grid, width, height, from, to); assert_eq!(path, array![8, 7, 6, 3].span()); } @@ -133,22 +133,22 @@ mod test { let height = 3; let from = 0; let to = 8; - let path = DFS::search(grid, width, height, from, to); + let path = DepthFirstSearch::search(grid, width, height, from, to); assert_eq!(path, array![].span()); } #[test] fn test_dfs_search_medium() { - // 1 x 0 0 15 14 13 12 - // 1 0 1 1 11 10 9 8 - // 1 1 1 1 7 6 5 4 - // 1 1 1 s 3 2 1 0 + // * x 0 0 + // * 0 * * + // * 1 * * + // * * * s let grid: felt252 = 0xCBFF; let width = 4; let height = 4; let from = 0; let to = 14; - let path = DFS::search(grid, width, height, from, to); + let path = DepthFirstSearch::search(grid, width, height, from, to); assert_eq!(path, array![14, 15, 11, 7, 3, 2, 1, 5, 9, 8, 4].span()); } @@ -162,7 +162,7 @@ mod test { let height = 2; let from = 3; let to = 2; - let path = DFS::search(grid, width, height, from, to); + let path = DepthFirstSearch::search(grid, width, height, from, to); assert_eq!(path, array![2].span()); } @@ -178,7 +178,7 @@ mod test { let height = 4; let from = 0; let to = 19; - let path = DFS::search(grid, width, height, from, to); + let path = DepthFirstSearch::search(grid, width, height, from, to); assert_eq!(path, array![19, 18, 13, 12, 11, 6, 1].span()); } } From 31a17b6c10b20509c71ad09ab0abd6dccdf24d92 Mon Sep 17 00:00:00 2001 From: guha-rahul <69rahul16@gmail.com> Date: Fri, 11 Oct 2024 22:32:15 +0530 Subject: [PATCH 5/5] test: added path to maze test --- crates/map/src/finders/dfs.cairo | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/map/src/finders/dfs.cairo b/crates/map/src/finders/dfs.cairo index 019d450..4d22619 100644 --- a/crates/map/src/finders/dfs.cairo +++ b/crates/map/src/finders/dfs.cairo @@ -169,10 +169,10 @@ mod test { #[test] fn test_dfs_maze() { // Grid representation: - // x 1 0 0 0 - // 0 1 1 1 0 - // 0 0 0 1 0 - // 1 1 1 1 s + // x * 0 0 0 + // 0 * * * 0 + // 0 0 0 * 0 + // 1 1 1 * s let grid: felt252 = 0xC385F; let width = 5; let height = 4;