use std::{error::Error, fmt};
use crate::{grid::Grid, path::Path, point::Point};
pub use condor_core::{
BudgetExhausted, BudgetWatch, SearchBudget, SearchOutcome, SearchPathCost, SearchVisitStats,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SearchRequest {
pub start: Point,
pub goal: Point,
pub budget: SearchBudget,
}
impl SearchRequest {
#[must_use]
pub const fn new(start: Point, goal: Point) -> Self {
Self {
start,
goal,
budget: SearchBudget::UNLIMITED,
}
}
#[must_use]
pub const fn with_budget(mut self, budget: SearchBudget) -> Self {
self.budget = budget;
self
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct SearchStats {
pub visited_nodes: usize,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GridSearchError {
InvalidStart { point: Point },
InvalidGoal { point: Point },
BudgetExhausted(BudgetExhausted),
StepLimitReached { max_steps: usize, reached: Point },
}
impl fmt::Display for GridSearchError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidStart { point } => write!(formatter, "invalid start point: {point:?}"),
Self::InvalidGoal { point } => write!(formatter, "invalid goal point: {point:?}"),
Self::BudgetExhausted(reason) => write!(formatter, "{reason}"),
Self::StepLimitReached { max_steps, reached } => write!(
formatter,
"search stopped after {max_steps} steps at {reached:?} before reaching the goal"
),
}
}
}
impl Error for GridSearchError {}
pub type SearchResult = Result<SearchOutcome<Path, SearchStats>, GridSearchError>;
#[doc(hidden)]
pub const fn found(path: Path, visited_nodes: usize) -> SearchResult {
Ok(SearchOutcome::found(path, SearchStats { visited_nodes }))
}
#[doc(hidden)]
pub const fn not_found(visited_nodes: usize) -> SearchResult {
Ok(SearchOutcome::no_path(SearchStats { visited_nodes }))
}
#[doc(hidden)]
pub fn validate_request(grid: &Grid, request: SearchRequest) -> Result<(), GridSearchError> {
if !grid.is_walkable(request.start) {
return Err(GridSearchError::InvalidStart {
point: request.start,
});
}
if !grid.is_walkable(request.goal) {
return Err(GridSearchError::InvalidGoal {
point: request.goal,
});
}
Ok(())
}
impl SearchPathCost for Path {
type Cost = usize;
fn path_cost(&self) -> Self::Cost {
self.cost()
}
}
impl SearchVisitStats for SearchStats {
fn visited_nodes(&self) -> usize {
self.visited_nodes
}
}
pub trait Pathfinder {
fn name(&self) -> &'static str;
fn search(&self, grid: &Grid, request: SearchRequest) -> SearchResult;
}
#[doc(hidden)]
pub const fn budget_error(reason: BudgetExhausted) -> GridSearchError {
GridSearchError::BudgetExhausted(reason)
}