Skip to content

Commit 0b780c0

Browse files
authored
feat/dfs (#110)
1 parent cbdb23c commit 0b780c0

File tree

2 files changed

+185
-0
lines changed

2 files changed

+185
-0
lines changed

crates/map/src/finders/dfs.cairo

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
//! Depth-First Search algorithm implementation for pathfinding.
2+
3+
// Core imports
4+
use core::dict::{Felt252Dict, Felt252DictTrait};
5+
6+
// Internal imports
7+
use origami_map::finders::finder::Finder;
8+
use origami_map::helpers::bitmap::Bitmap;
9+
use origami_map::helpers::seeder::Seeder;
10+
use origami_map::types::node::{Node, NodeTrait};
11+
use origami_map::types::direction::{Direction, DirectionTrait};
12+
13+
/// DepthFirstSearch implementation for pathfinding
14+
#[generate_trait]
15+
pub impl DepthFirstSearch of DepthFirstSearchTrait {
16+
/// Searches for a path from 'from' to 'to' on the given grid using DepthFirstSearch
17+
///
18+
/// # Arguments
19+
/// * `grid` - The grid represented as a felt252
20+
/// * `width` - The width of the grid
21+
/// * `height` - The height of the grid
22+
/// * `from` - The starting position
23+
/// * `to` - The target position
24+
///
25+
/// # Returns
26+
/// A Span<u8> representing the path from 'from' to 'to', or an empty span if no path exists
27+
#[inline]
28+
fn search(grid: felt252, width: u8, height: u8, from: u8, to: u8) -> Span<u8> {
29+
// [Check] The start and target are walkable
30+
if Bitmap::get(grid, from) == 0 || Bitmap::get(grid, to) == 0 {
31+
return array![].span();
32+
}
33+
34+
// [Effect] Initialize the start and target nodes
35+
let start = NodeTrait::new(from, 0, 0, 0);
36+
let target = NodeTrait::new(to, 0, 0, 0);
37+
38+
// [Effect] Initialize visited nodes and parents
39+
let mut visited: Felt252Dict<bool> = Default::default();
40+
let mut parents: Felt252Dict<u8> = Default::default();
41+
42+
// [Compute] Start the recursive DFS
43+
let found = Self::iter(grid, width, height, start, target, ref visited, ref parents);
44+
45+
// Reconstruct and return the path if found
46+
if found {
47+
Finder::path_with_parents(ref parents, start, target)
48+
} else {
49+
array![].span()
50+
}
51+
}
52+
53+
/// Recursive helper function for DFS
54+
#[inline]
55+
fn iter(
56+
grid: felt252,
57+
width: u8,
58+
height: u8,
59+
current: Node,
60+
target: Node,
61+
ref visited: Felt252Dict<bool>,
62+
ref parents: Felt252Dict<u8>
63+
) -> bool {
64+
// [Check] If the current node has already been visited, return false
65+
if visited.get(current.position.into()) {
66+
return false;
67+
}
68+
69+
// [Check] Mark current node as visited
70+
visited.insert(current.position.into(), true);
71+
72+
// [Check] If we've reached the target, we're done
73+
if current.position == target.position {
74+
return true;
75+
}
76+
77+
// [Compute] Evaluate the neighbors for all 4 directions
78+
let seed = Seeder::shuffle(grid, current.position.into());
79+
let mut directions = DirectionTrait::compute_shuffled_directions(seed);
80+
let mut found = false;
81+
82+
loop {
83+
if directions == 0 {
84+
break;
85+
}
86+
let direction = DirectionTrait::pop_front(ref directions);
87+
if Finder::check(grid, width, height, current.position, direction, ref visited) {
88+
let neighbor_position = direction.next(current.position, width);
89+
let neighbor = NodeTrait::new(neighbor_position, current.position, 0, 0);
90+
91+
// [Effect] Set parent for the neighbor
92+
parents.insert(neighbor_position.into(), current.position);
93+
94+
// [Recurse] Continue DFS from the neighbor
95+
found = Self::iter(grid, width, height, neighbor, target, ref visited, ref parents);
96+
97+
if found {
98+
break;
99+
}
100+
}
101+
};
102+
103+
// [Check] Return whether we've found the target
104+
found
105+
}
106+
}
107+
108+
#[cfg(test)]
109+
mod test {
110+
use super::DepthFirstSearch;
111+
112+
#[test]
113+
fn test_dfs_search_small() {
114+
// x * *
115+
// 1 0 *
116+
// 0 1 s
117+
let grid: felt252 = 0x1EB;
118+
let width = 3;
119+
let height = 3;
120+
let from = 0;
121+
let to = 8;
122+
let path = DepthFirstSearch::search(grid, width, height, from, to);
123+
assert_eq!(path, array![8, 7, 6, 3].span());
124+
}
125+
126+
#[test]
127+
fn test_dfs_search_impossible() {
128+
// x 1 0
129+
// 1 0 1
130+
// 0 1 s
131+
let grid: felt252 = 0x1AB;
132+
let width = 3;
133+
let height = 3;
134+
let from = 0;
135+
let to = 8;
136+
let path = DepthFirstSearch::search(grid, width, height, from, to);
137+
assert_eq!(path, array![].span());
138+
}
139+
140+
#[test]
141+
fn test_dfs_search_medium() {
142+
// * x 0 0
143+
// * 0 * *
144+
// * 1 * *
145+
// * * * s
146+
let grid: felt252 = 0xCBFF;
147+
let width = 4;
148+
let height = 4;
149+
let from = 0;
150+
let to = 14;
151+
let path = DepthFirstSearch::search(grid, width, height, from, to);
152+
assert_eq!(path, array![14, 15, 11, 7, 3, 2, 1, 5, 9, 8, 4].span());
153+
}
154+
155+
#[test]
156+
fn test_dfs_single_cell_path() {
157+
// Grid representation:
158+
// x s
159+
// 1 1
160+
let grid: felt252 = 0xF;
161+
let width = 2;
162+
let height = 2;
163+
let from = 3;
164+
let to = 2;
165+
let path = DepthFirstSearch::search(grid, width, height, from, to);
166+
assert_eq!(path, array![2].span());
167+
}
168+
169+
#[test]
170+
fn test_dfs_maze() {
171+
// Grid representation:
172+
// x * 0 0 0
173+
// 0 * * * 0
174+
// 0 0 0 * 0
175+
// 1 1 1 * s
176+
let grid: felt252 = 0xC385F;
177+
let width = 5;
178+
let height = 4;
179+
let from = 0;
180+
let to = 19;
181+
let path = DepthFirstSearch::search(grid, width, height, from, to);
182+
assert_eq!(path, array![19, 18, 13, 12, 11, 6, 1].span());
183+
}
184+
}

crates/map/src/lib.cairo

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub mod finders {
1111
pub mod astar;
1212
pub mod bfs;
1313
pub mod greedy;
14+
pub mod dfs;
1415
}
1516

1617
pub mod generators {

0 commit comments

Comments
 (0)