///|
/// Cardinal direction offsets (N, S, E, W).
let four_way_offsets : Array[(Int, Int)] = [
(0, -1), // North
(0, 1), // South
(1, 0), // East
(-1, 0), // West
]
///|
/// Returns the list of passable neighbors for a point using four-way movement.
///
/// Skips out-of-bounds cells, blocked cells, and cells with negative cost.
pub fn four_way_neighbors(grid : Grid, p : Point) -> Array[Point] {
let neighbors = Array::new()
if !grid.is_passable(p) {
return neighbors
}
for i = 0; i < four_way_offsets.length(); i = i + 1 {
let (dx, dy) = four_way_offsets[i]
let nx = p.x + dx
let ny = p.y + dy
if grid.is_passable_xy(nx, ny) {
neighbors.push(Point::new(nx, ny))
}
}
neighbors
}
///|
/// Returns the list of passable neighbors as (Point, cost) pairs.
///
/// Useful for weighted pathfinding where different moves may have
/// different traversal costs.
pub fn four_way_neighbors_with_cost(
grid : Grid,
p : Point,
) -> Array[(Point, Double)] {
let neighbors = Array::new()
if !grid.is_passable(p) {
return neighbors
}
for i = 0; i < four_way_offsets.length(); i = i + 1 {
let (dx, dy) = four_way_offsets[i]
let nx = p.x + dx
let ny = p.y + dy
if grid.is_passable_xy(nx, ny) {
let cost = grid.cost(nx, ny)
neighbors.push((Point::new(nx, ny), cost))
}
}
neighbors
}
///|
/// Cardinal + diagonal direction offsets.
let eight_way_offsets : Array[(Int, Int)] = [
(0, -1), // N
(1, -1), // NE
(1, 0), // E
(1, 1), // SE
(0, 1), // S
(-1, 1), // SW
(-1, 0), // W
(-1, -1), // NW
]
///|
/// Returns the list of passable neighbors for a point using eight-way movement.
///
/// Includes diagonal moves without cutting through blocked corners. A
/// diagonal destination and both adjacent cardinal cells must be passable.
pub fn eight_way_neighbors(grid : Grid, p : Point) -> Array[Point] {
let neighbors = Array::new()
if !grid.is_passable(p) {
return neighbors
}
for i = 0; i < eight_way_offsets.length(); i = i + 1 {
let (dx, dy) = eight_way_offsets[i]
let nx = p.x + dx
let ny = p.y + dy
if grid.is_passable_xy(nx, ny) && can_move_diagonally(grid, p, dx, dy) {
neighbors.push(Point::new(nx, ny))
}
}
neighbors
}
///|
/// Returns the list of passable neighbors as (Point, cost) pairs
/// for eight-way movement.
///
/// Diagonal moves use cost * sqrt(2) for the Euclidean distance.
/// Diagonal corner cutting is not allowed.
pub fn eight_way_neighbors_with_cost(
grid : Grid,
p : Point,
) -> Array[(Point, Double)] {
let neighbors = Array::new()
if !grid.is_passable(p) {
return neighbors
}
for i = 0; i < eight_way_offsets.length(); i = i + 1 {
let (dx, dy) = eight_way_offsets[i]
let nx = p.x + dx
let ny = p.y + dy
if grid.is_passable_xy(nx, ny) && can_move_diagonally(grid, p, dx, dy) {
let base_cost = grid.cost(nx, ny)
let move_cost = if dx != 0 && dy != 0 {
base_cost * 1.4142135623730951 // sqrt(2)
} else {
base_cost
}
neighbors.push((Point::new(nx, ny), move_cost))
}
}
neighbors
}
///|
fn can_move_diagonally(grid : Grid, p : Point, dx : Int, dy : Int) -> Bool {
if dx == 0 || dy == 0 {
return true
}
grid.is_passable_xy(p.x + dx, p.y) && grid.is_passable_xy(p.x, p.y + dy)
}