use glam::Vec2; use crate::systems::movement::NodeId; use super::direction::Direction; use bitflags::bitflags; bitflags! { /// Defines who can traverse a given edge using flags for fast checking. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct TraversalFlags: u8 { const PACMAN = 1 << 0; const GHOST = 1 << 1; /// Convenience flag for edges that all entities can use const ALL = Self::PACMAN.bits() | Self::GHOST.bits(); } } /// Represents a directed edge from one node to another with a given weight (e.g., distance). #[derive(Debug, Clone, Copy)] pub struct Edge { /// The destination node of this edge. pub target: NodeId, /// The length of the edge. pub distance: f32, /// The cardinal direction of this edge. pub direction: Direction, /// Defines who is allowed to traverse this edge. pub traversal_flags: TraversalFlags, } /// Represents a node in the graph, defined by its position. #[derive(Debug)] pub struct Node { /// The 2D coordinates of the node. pub position: Vec2, } /// Represents the four possible directions from a node in the graph. /// /// Each field contains an optional edge leading in that direction. /// This structure is used to represent the adjacency list for each node, /// providing O(1) access to edges in any cardinal direction. #[derive(Debug, Default)] pub struct Intersection { /// Edge leading upward from this node, if it exists. pub up: Option, /// Edge leading downward from this node, if it exists. pub down: Option, /// Edge leading leftward from this node, if it exists. pub left: Option, /// Edge leading rightward from this node, if it exists. pub right: Option, } impl Intersection { /// Returns an iterator over all edges from this intersection. /// /// This iterator yields only the edges that exist (non-None values). pub fn edges(&self) -> impl Iterator { [self.up, self.down, self.left, self.right].into_iter().flatten() } /// Retrieves the edge in the specified direction, if it exists. pub fn get(&self, direction: Direction) -> Option { match direction { Direction::Up => self.up, Direction::Down => self.down, Direction::Left => self.left, Direction::Right => self.right, } } /// Sets the edge in the specified direction. /// /// This will overwrite any existing edge in that direction. pub fn set(&mut self, direction: Direction, edge: Edge) { match direction { Direction::Up => self.up = Some(edge), Direction::Down => self.down = Some(edge), Direction::Left => self.left = Some(edge), Direction::Right => self.right = Some(edge), } } } /// A directed graph structure using an adjacency list representation. /// /// Nodes are stored in a vector, and their indices serve as their `NodeId`. /// This design provides fast, O(1) lookups for node data. Edges are stored /// in an adjacency list, where each node has a list of outgoing edges. pub struct Graph { nodes: Vec, pub adjacency_list: Vec, } impl Graph { /// Creates a new, empty graph. pub fn new() -> Self { Graph { nodes: Vec::new(), adjacency_list: Vec::new(), } } /// Adds a new node with the given data to the graph and returns its ID. pub fn add_node(&mut self, data: Node) -> NodeId { let id = self.nodes.len() as NodeId; self.nodes.push(data); self.adjacency_list.push(Intersection::default()); id } /// Connects a new node to the graph and adds an edge between the existing node and the new node. pub fn add_connected(&mut self, from: NodeId, direction: Direction, new_node: Node) -> Result { let to = self.add_node(new_node); self.connect(from, to, false, None, direction)?; Ok(to) } /// Connects two existing nodes with an edge. pub fn connect( &mut self, from: NodeId, to: NodeId, replace: bool, distance: Option, direction: Direction, ) -> Result<(), &'static str> { if from as usize >= self.adjacency_list.len() { return Err("From node does not exist."); } if to as usize >= self.adjacency_list.len() { return Err("To node does not exist."); } let edge_a = self.add_edge(from, to, replace, distance, direction, TraversalFlags::ALL); let edge_b = self.add_edge(to, from, replace, distance, direction.opposite(), TraversalFlags::ALL); if edge_a.is_err() && edge_b.is_err() { return Err("Failed to connect nodes in both directions."); } Ok(()) } /// Adds a directed edge between two nodes. /// /// If `distance` is `None`, it will be calculated automatically based on the /// Euclidean distance between the two nodes. /// /// # Errors /// /// Returns an error if: /// - The `from` node does not exist /// - An edge already exists in the specified direction /// - An edge already exists to the target node /// - The provided distance is not positive pub fn add_edge( &mut self, from: NodeId, to: NodeId, replace: bool, distance: Option, direction: Direction, traversal_flags: TraversalFlags, ) -> Result<(), &'static str> { let edge = Edge { target: to, distance: match distance { Some(distance) => { if distance < 0.0 { return Err("Edge distance must be on-negative."); } distance } None => { // If no distance is provided, calculate it based on the positions of the nodes let from_pos = self.nodes[from as usize].position; let to_pos = self.nodes[to as usize].position; from_pos.distance(to_pos) } }, direction, traversal_flags, }; if from as usize >= self.adjacency_list.len() { return Err("From node does not exist."); } let adjacency_list = &mut self.adjacency_list[from as usize]; // Check if the edge already exists in this direction or to the same target if let Some(err) = adjacency_list.edges().find_map(|e| { if !replace { // If we're not replacing the edge, we don't want to replace an edge that already exists in this direction if e.direction == direction { return Some(Err("Edge already exists in this direction.")); } else if e.target == to { return Some(Err("Edge already exists.")); } } None }) { return err; } adjacency_list.set(direction, edge); Ok(()) } /// Retrieves an immutable reference to a node's data. pub fn get_node(&self, id: NodeId) -> Option<&Node> { self.nodes.get(id as usize) } /// Returns an iterator over all nodes in the graph. pub fn nodes(&self) -> impl Iterator { self.nodes.iter() } /// Returns an iterator over all edges in the graph. pub fn edges(&self) -> impl Iterator + '_ { self.adjacency_list .iter() .enumerate() .flat_map(|(node_id, intersection)| intersection.edges().map(move |edge| (node_id as NodeId, edge))) } /// Finds a specific edge from a source node to a target node. pub fn find_edge(&self, from: NodeId, to: NodeId) -> Option { self.adjacency_list.get(from as usize)?.edges().find(|edge| edge.target == to) } /// Finds an edge originating from a given node that follows a specific direction. pub fn find_edge_in_direction(&self, from: NodeId, direction: Direction) -> Option { self.adjacency_list.get(from as usize)?.get(direction) } } // Default implementation for creating an empty graph. impl Default for Graph { fn default() -> Self { Self::new() } }