Skip to main content

condor_grid/
path.rs

1//! Validated discrete path value: ordered cells plus an algorithm-reported cost.
2//!
3//! [`Path`] is non-empty by construction. [`Path::from_steps`] records hop count;
4//! [`Path::from_steps_with_cost`] lets weighted algorithms preserve true traversal
5//! cost. It is the path payload in [`crate::SearchOutcome`], not a map-validating
6//! route checker.
7
8use crate::point::Point;
9
10/// Path construction failed because the step list was empty or otherwise invalid.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
12#[non_exhaustive]
13pub enum PathBuildError {
14    /// `from_steps*` require at least one cell.
15    #[error("paths must contain at least one point")]
16    Empty,
17}
18
19/// Non-empty sequence of grid cells with an associated traversal cost.
20///
21/// Cost is hop-count (`from_steps`) or algorithm-supplied weighted cost
22/// (`from_steps_with_cost`). Paths do not validate adjacency on construction;
23/// callers use [`crate::Grid::path_is_walkable`] when geometry must be checked.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct Path {
26    steps: Vec<Point>,
27    cost: usize,
28}
29
30impl Path {
31    /// Builds a path with cost equal to `steps.len() - 1` (unit hop model).
32    ///
33    /// # Errors
34    ///
35    /// Returns [`PathBuildError::Empty`] when `steps` is empty.
36    pub fn from_steps(steps: Vec<Point>) -> Result<Self, PathBuildError> {
37        if steps.is_empty() {
38            return Err(PathBuildError::Empty);
39        }
40        let cost = steps.len().saturating_sub(1);
41        Ok(Self { steps, cost })
42    }
43
44    /// Builds a path with an explicit traversal cost (weighted solvers).
45    ///
46    /// # Errors
47    ///
48    /// Returns [`PathBuildError::Empty`] when `steps` is empty.
49    pub fn from_steps_with_cost(steps: Vec<Point>, cost: usize) -> Result<Self, PathBuildError> {
50        if steps.is_empty() {
51            return Err(PathBuildError::Empty);
52        }
53        Ok(Self { steps, cost })
54    }
55
56    /// Ordered cell sequence from start through goal (inclusive).
57    #[must_use]
58    pub fn steps(&self) -> &[Point] {
59        &self.steps
60    }
61
62    /// Number of cell steps (always ≥ 1 for constructed paths).
63    #[must_use]
64    pub fn len(&self) -> usize {
65        self.steps.len()
66    }
67
68    /// Always `false` for successfully constructed paths.
69    #[must_use]
70    pub const fn is_empty(&self) -> bool {
71        false
72    }
73
74    /// Hop count (`from_steps`) or algorithm-supplied traversal cost (`from_steps_with_cost`).
75    #[must_use]
76    pub fn cost(&self) -> usize {
77        self.cost
78    }
79
80    /// First step; always defined because paths are non-empty by construction.
81    #[must_use]
82    pub fn start(&self) -> Point {
83        self.steps[0]
84    }
85
86    /// Last step; always defined because paths are non-empty by construction.
87    #[must_use]
88    pub fn goal(&self) -> Point {
89        self.steps[self.steps.len() - 1]
90    }
91}