Skip to content

Commit 4d6a98e

Browse files
JJScarJordan Solomonbal7hazar
authored
Feature/dijkstra pathfinding (#119)
Co-authored-by: Jordan Solomon <[email protected]> Co-authored-by: bal7hazar <[email protected]>
1 parent fb5dc62 commit 4d6a98e

File tree

3 files changed

+178
-41
lines changed

3 files changed

+178
-41
lines changed

crates/map/src/finders/astar.cairo

Lines changed: 4 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -49,22 +49,22 @@ pub impl Astar of AstarTrait {
4949
let seed = Seeder::shuffle(grid, current.position.into());
5050
let mut directions = DirectionTrait::compute_shuffled_directions(seed);
5151
let direction: Direction = DirectionTrait::pop_front(ref directions);
52-
if Self::check(grid, width, height, current.position, direction, ref visited) {
52+
if Finder::check(grid, width, height, current.position, direction, ref visited) {
5353
let neighbor_position = direction.next(current.position, width);
5454
Self::assess(width, neighbor_position, current, target, ref heap);
5555
}
5656
let direction: Direction = DirectionTrait::pop_front(ref directions);
57-
if Self::check(grid, width, height, current.position, direction, ref visited) {
57+
if Finder::check(grid, width, height, current.position, direction, ref visited) {
5858
let neighbor_position = direction.next(current.position, width);
5959
Self::assess(width, neighbor_position, current, target, ref heap);
6060
}
6161
let direction: Direction = DirectionTrait::pop_front(ref directions);
62-
if Self::check(grid, width, height, current.position, direction, ref visited) {
62+
if Finder::check(grid, width, height, current.position, direction, ref visited) {
6363
let neighbor_position = direction.next(current.position, width);
6464
Self::assess(width, neighbor_position, current, target, ref heap);
6565
}
6666
let direction: Direction = DirectionTrait::pop_front(ref directions);
67-
if Self::check(grid, width, height, current.position, direction, ref visited) {
67+
if Finder::check(grid, width, height, current.position, direction, ref visited) {
6868
let neighbor_position = direction.next(current.position, width);
6969
Self::assess(width, neighbor_position, current, target, ref heap);
7070
}
@@ -74,43 +74,6 @@ pub impl Astar of AstarTrait {
7474
Finder::path_with_heap(ref heap, start, target)
7575
}
7676

77-
/// Check if the position can be visited in the specified direction.
78-
/// # Arguments
79-
/// * `grid` - The grid to search (1 is walkable and 0 is not)
80-
/// * `width` - The width of the grid
81-
/// * `height` - The height of the grid
82-
/// * `position` - The current position
83-
/// * `direction` - The direction to check
84-
/// * `visited` - The visited nodes
85-
/// # Returns
86-
/// * Whether the position can be visited in the specified direction
87-
#[inline]
88-
fn check(
89-
grid: felt252,
90-
width: u8,
91-
height: u8,
92-
position: u8,
93-
direction: Direction,
94-
ref visited: Felt252Dict<bool>,
95-
) -> bool {
96-
let (x, y) = (position % width, position / width);
97-
match direction {
98-
Direction::North => (y < height - 1)
99-
&& (Bitmap::get(grid, position + width) == 1)
100-
&& !visited.get((position + width).into()),
101-
Direction::East => (x > 0)
102-
&& (Bitmap::get(grid, position - 1) == 1)
103-
&& !visited.get((position - 1).into()),
104-
Direction::South => (y > 0)
105-
&& (Bitmap::get(grid, position - width) == 1)
106-
&& !visited.get((position - width).into()),
107-
Direction::West => (x < width - 1)
108-
&& (Bitmap::get(grid, position + 1) == 1)
109-
&& !visited.get((position + 1).into()),
110-
_ => false,
111-
}
112-
}
113-
11477
/// Assess the neighbor node and update the heap.
11578
/// # Arguments
11679
/// * `width` - The width of the grid
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
//! Dijkstra algorithm 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::finders::astar::Astar;
9+
use origami_map::helpers::heap::{Heap, HeapTrait};
10+
use origami_map::helpers::bitmap::Bitmap;
11+
use origami_map::types::node::{Node, NodeTrait};
12+
use origami_map::types::direction::{Direction, DirectionTrait};
13+
use origami_map::helpers::seeder::Seeder;
14+
15+
#[generate_trait]
16+
pub impl Dijkstra of DijkstraTrait {
17+
/// Search for the shortest path from a start to a target position using Dijkstra's algorithm.
18+
/// # Arguments
19+
/// * `grid` - The grid to search (1 is walkable and 0 is not)
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+
/// # Returns
25+
/// * The path from the target (incl.) to the start (excl.)
26+
#[inline]
27+
fn search(grid: felt252, width: u8, height: u8, from: u8, to: u8) -> Span<u8> {
28+
// [Check] The start and target are walkable
29+
if Bitmap::get(grid, from) == 0 || Bitmap::get(grid, to) == 0 {
30+
return array![].span();
31+
}
32+
// [Effect] Initialize the start and target nodes
33+
let mut start = NodeTrait::new(from, 0, 0, 0);
34+
let target = NodeTrait::new(to, 0, 0, 0);
35+
// [Effect] Initialize the heap and the visited nodes
36+
let mut heap: Heap<Node> = HeapTrait::new();
37+
let mut visited: Felt252Dict<bool> = Default::default();
38+
heap.add(start);
39+
// [Compute] Evaluate the path until the target is reached
40+
while let Option::Some(current) = heap.pop_front() {
41+
// [Compute] Get the less expensive node
42+
visited.insert(current.position.into(), true);
43+
// [Check] Stop if we reached the target
44+
if current.position == target.position {
45+
break;
46+
}
47+
// [Compute] Evaluate the neighbors for all 4 directions
48+
let seed = Seeder::shuffle(grid, current.position.into());
49+
let mut directions = DirectionTrait::compute_shuffled_directions(seed);
50+
let direction: Direction = DirectionTrait::pop_front(ref directions);
51+
if Finder::check(grid, width, height, current.position, direction, ref visited) {
52+
let neighbor_position = direction.next(current.position, width);
53+
Self::assess(width, neighbor_position, current, target, ref heap);
54+
}
55+
let direction: Direction = DirectionTrait::pop_front(ref directions);
56+
if Finder::check(grid, width, height, current.position, direction, ref visited) {
57+
let neighbor_position = direction.next(current.position, width);
58+
Self::assess(width, neighbor_position, current, target, ref heap);
59+
}
60+
let direction: Direction = DirectionTrait::pop_front(ref directions);
61+
if Finder::check(grid, width, height, current.position, direction, ref visited) {
62+
let neighbor_position = direction.next(current.position, width);
63+
Self::assess(width, neighbor_position, current, target, ref heap);
64+
}
65+
let direction: Direction = DirectionTrait::pop_front(ref directions);
66+
if Finder::check(grid, width, height, current.position, direction, ref visited) {
67+
let neighbor_position = direction.next(current.position, width);
68+
Self::assess(width, neighbor_position, current, target, ref heap);
69+
}
70+
};
71+
72+
// [Return] Reconstruct the path from the start to the target
73+
Finder::path_with_heap(ref heap, start, NodeTrait::new(to, 0, 0, 0))
74+
}
75+
76+
/// Assess a neighbor node (simplified from A*).
77+
#[inline]
78+
fn assess(width: u8, neighbor_position: u8, current: Node, target: Node, ref heap: Heap<Node>) {
79+
let distance = Finder::manhattan(current.position, neighbor_position, width);
80+
let neighbor_gcost = current.gcost + distance;
81+
let mut neighbor = match heap.get(neighbor_position.into()) {
82+
Option::Some(node) => node,
83+
Option::None => NodeTrait::new(neighbor_position, current.position, neighbor_gcost, 0),
84+
};
85+
if neighbor_gcost < neighbor.gcost || !heap.contains(neighbor.position) {
86+
neighbor.gcost = neighbor_gcost;
87+
neighbor.source = current.position;
88+
if !heap.contains(neighbor.position) {
89+
return heap.add(neighbor);
90+
}
91+
return heap.update(neighbor);
92+
}
93+
}
94+
}
95+
96+
// => Tests <=//
97+
#[cfg(test)]
98+
mod test {
99+
// Local imports
100+
use super::Dijkstra;
101+
102+
#[test]
103+
fn test_dijkstra_search_small() {
104+
// x * *
105+
// 1 0 *
106+
// 0 1 s
107+
let grid: felt252 = 0x1EB;
108+
let width = 3;
109+
let height = 3;
110+
let from = 0;
111+
let to = 8;
112+
let mut path = Dijkstra::search(grid, width, height, from, to);
113+
assert_eq!(path, array![8, 7, 6, 3].span());
114+
}
115+
116+
#[test]
117+
fn test_dijkstra_search_impossible() {
118+
// x 1 0
119+
// 1 0 1
120+
// 0 1 s
121+
let grid: felt252 = 0x1AB;
122+
let width = 3;
123+
let height = 3;
124+
let from = 0;
125+
let to = 8;
126+
let mut path = Dijkstra::search(grid, width, height, from, to);
127+
assert_eq!(path, array![].span());
128+
}
129+
130+
#[test]
131+
fn test_dijkstra_search_medium() {
132+
// * x 0 0
133+
// * 0 1 1
134+
// * 1 1 1
135+
// * * * s
136+
let grid: felt252 = 0xCBFF;
137+
let width = 4;
138+
let height = 4;
139+
let from = 0;
140+
let to = 14;
141+
let mut path = Dijkstra::search(grid, width, height, from, to);
142+
assert_eq!(path, array![14, 15, 11, 7, 3, 2, 1].span());
143+
}
144+
145+
#[test]
146+
fn test_dijkstra_search_large() {
147+
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
148+
// 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0
149+
// 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0
150+
// 0 0 1 1 1 1 1 0 1 1 0 0 0 0 0 0 0 0
151+
// 0 0 0 1 1 1 1 * * x 0 0 0 0 0 0 0 0
152+
// 0 0 0 0 1 1 1 * 0 0 0 1 0 0 1 0 0 0
153+
// 0 0 0 1 1 1 1 * 0 0 0 1 1 1 1 1 0 0
154+
// 0 0 1 1 1 1 1 * * 1 1 1 1 1 1 1 1 0
155+
// 0 0 0 1 1 1 1 0 * 1 1 0 1 1 1 1 1 0
156+
// 0 0 0 0 1 1 1 1 * * 1 1 1 1 1 1 1 0
157+
// 0 0 0 1 1 1 1 1 1 * * * * * * * s 0
158+
// 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0
159+
// 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0
160+
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
161+
let grid: felt252 = 0x7F003F800FB001FC003C481F1F0FFFE1EEF83FFE1FFF81FFE03FF80000;
162+
let width = 18;
163+
let height = 14;
164+
let from = 55;
165+
let to = 170;
166+
let mut path = Dijkstra::search(grid, width, height, from, to);
167+
assert_eq!(
168+
path,
169+
array![170, 171, 172, 154, 136, 118, 117, 99, 81, 80, 62, 61, 60, 59, 58, 57, 56]
170+
.span(),
171+
);
172+
}
173+
}

crates/map/src/lib.cairo

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub mod finders {
1212
pub mod bfs;
1313
pub mod greedy;
1414
pub mod dfs;
15+
pub mod dijkstra;
1516
}
1617

1718
pub mod generators {

0 commit comments

Comments
 (0)