condor-pathfinding-grid 0.4.0

Grid pathfinding, preprocessing, replanning, and multi-agent algorithms for Condor.
Documentation
//! Validated discrete path value: ordered cells plus an algorithm-reported cost.
//!
//! [`Path`] is non-empty by construction. [`Path::from_steps`] records hop count;
//! [`Path::from_steps_with_cost`] lets weighted algorithms preserve true traversal
//! cost. It is the path payload in [`crate::SearchOutcome`], not a map-validating
//! route checker.

use crate::point::Point;

/// Path construction failed because the step list was empty or otherwise invalid.
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum PathBuildError {
    /// `from_steps*` require at least one cell.
    #[error("paths must contain at least one point")]
    Empty,
}

/// Non-empty sequence of grid cells with an associated traversal cost.
///
/// Cost is hop-count (`from_steps`) or algorithm-supplied weighted cost
/// (`from_steps_with_cost`). Paths do not validate adjacency on construction;
/// callers use [`crate::Grid::path_is_walkable`] when geometry must be checked.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Path {
    steps: Vec<Point>,
    cost: usize,
}

impl Path {
    /// Builds a path with cost equal to `steps.len() - 1` (unit hop model).
    ///
    /// # Errors
    ///
    /// Returns [`PathBuildError::Empty`] when `steps` is empty.
    pub fn from_steps(steps: Vec<Point>) -> Result<Self, PathBuildError> {
        if steps.is_empty() {
            return Err(PathBuildError::Empty);
        }
        let cost = steps.len().saturating_sub(1);
        Ok(Self { steps, cost })
    }

    /// Builds a path with an explicit traversal cost (weighted solvers).
    ///
    /// # Errors
    ///
    /// Returns [`PathBuildError::Empty`] when `steps` is empty.
    pub fn from_steps_with_cost(steps: Vec<Point>, cost: usize) -> Result<Self, PathBuildError> {
        if steps.is_empty() {
            return Err(PathBuildError::Empty);
        }
        Ok(Self { steps, cost })
    }

    /// Ordered cell sequence from start through goal (inclusive).
    #[must_use]
    pub fn steps(&self) -> &[Point] {
        &self.steps
    }

    /// Number of cell steps (always ≥ 1 for constructed paths).
    #[must_use]
    pub fn len(&self) -> usize {
        self.steps.len()
    }

    /// Always `false` for successfully constructed paths.
    #[must_use]
    pub const fn is_empty(&self) -> bool {
        false
    }

    /// Hop count (`from_steps`) or algorithm-supplied traversal cost (`from_steps_with_cost`).
    #[must_use]
    pub fn cost(&self) -> usize {
        self.cost
    }

    /// First step; always defined because paths are non-empty by construction.
    #[must_use]
    pub fn start(&self) -> Point {
        self.steps[0]
    }

    /// Last step; always defined because paths are non-empty by construction.
    #[must_use]
    pub fn goal(&self) -> Point {
        self.steps[self.steps.len() - 1]
    }
}