mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-09 14:07:57 -06:00
773 lines
25 KiB
Rust
773 lines
25 KiB
Rust
use glam::Vec2;
|
|
|
|
use super::direction::Direction;
|
|
|
|
/// A unique identifier for a node, represented by its index in the graph's storage.
|
|
pub type NodeId = usize;
|
|
|
|
/// 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,
|
|
}
|
|
|
|
/// 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>,
|
|
/// Edge leading downward from this node, if it exists.
|
|
pub down: Option<Edge>,
|
|
/// Edge leading leftward from this node, if it exists.
|
|
pub left: Option<Edge>,
|
|
/// Edge leading rightward from this node, if it exists.
|
|
pub right: Option<Edge>,
|
|
}
|
|
|
|
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<Item = Edge> {
|
|
[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<Edge> {
|
|
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<Node>,
|
|
pub adjacency_list: Vec<Intersection>,
|
|
}
|
|
|
|
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();
|
|
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 connect_node(&mut self, from: NodeId, direction: Direction, new_node: Node) -> Result<NodeId, &'static str> {
|
|
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<f32>,
|
|
direction: Direction,
|
|
) -> Result<(), &'static str> {
|
|
if from >= self.adjacency_list.len() {
|
|
return Err("From node does not exist.");
|
|
}
|
|
if to >= self.adjacency_list.len() {
|
|
return Err("To node does not exist.");
|
|
}
|
|
|
|
let edge_a = self.add_edge(from, to, replace, distance, direction);
|
|
let edge_b = self.add_edge(to, from, replace, distance, direction.opposite());
|
|
|
|
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<f32>,
|
|
direction: Direction,
|
|
) -> 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].position;
|
|
let to_pos = self.nodes[to].position;
|
|
from_pos.distance(to_pos)
|
|
}
|
|
},
|
|
direction,
|
|
};
|
|
|
|
if from >= self.adjacency_list.len() {
|
|
return Err("From node does not exist.");
|
|
}
|
|
|
|
let adjacency_list = &mut self.adjacency_list[from];
|
|
|
|
// 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 we're not replacing the edge, we don't want to replace an edge that already exists in this direction
|
|
if !replace && e.direction == direction {
|
|
Some(Err("Edge already exists in this direction."))
|
|
} else if e.target == to {
|
|
Some(Err("Edge already exists."))
|
|
} else {
|
|
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)
|
|
}
|
|
|
|
/// Returns the total number of nodes in the graph.
|
|
pub fn node_count(&self) -> usize {
|
|
self.nodes.len()
|
|
}
|
|
|
|
/// Finds a specific edge from a source node to a target node.
|
|
pub fn find_edge(&self, from: NodeId, to: NodeId) -> Option<Edge> {
|
|
self.adjacency_list.get(from)?.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<Edge> {
|
|
self.adjacency_list.get(from)?.get(direction)
|
|
}
|
|
}
|
|
|
|
// Default implementation for creating an empty graph.
|
|
impl Default for Graph {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
// --- Traversal State and Logic ---
|
|
|
|
/// Represents the current position of an entity traversing the graph.
|
|
///
|
|
/// This enum allows for precise tracking of whether an entity is exactly at a node
|
|
/// or moving along an edge between two nodes.
|
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
|
pub enum Position {
|
|
/// The traverser is located exactly at a node.
|
|
AtNode(NodeId),
|
|
/// The traverser is on an edge between two nodes.
|
|
BetweenNodes {
|
|
from: NodeId,
|
|
to: NodeId,
|
|
/// The floating-point distance traversed along the edge from the `from` node.
|
|
traversed: f32,
|
|
},
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
impl Position {
|
|
/// Returns `true` if the position is exactly at a node.
|
|
pub fn is_at_node(&self) -> bool {
|
|
matches!(self, Position::AtNode(_))
|
|
}
|
|
|
|
/// Returns the `NodeId` of the current or most recently departed node.
|
|
#[allow(clippy::wrong_self_convention)]
|
|
pub fn from_node_id(&self) -> NodeId {
|
|
match self {
|
|
Position::AtNode(id) => *id,
|
|
Position::BetweenNodes { from, .. } => *from,
|
|
}
|
|
}
|
|
|
|
/// Returns the `NodeId` of the destination node, if currently on an edge.
|
|
#[allow(clippy::wrong_self_convention)]
|
|
pub fn to_node_id(&self) -> Option<NodeId> {
|
|
match self {
|
|
Position::AtNode(_) => None,
|
|
Position::BetweenNodes { to, .. } => Some(*to),
|
|
}
|
|
}
|
|
|
|
/// Returns `true` if the traverser is stopped at a node.
|
|
pub fn is_stopped(&self) -> bool {
|
|
matches!(self, Position::AtNode(_))
|
|
}
|
|
}
|
|
|
|
/// Manages an entity's movement through the graph.
|
|
///
|
|
/// A `Traverser` encapsulates the state of an entity's position and direction,
|
|
/// providing a way to advance along the graph's paths based on a given distance.
|
|
/// It also handles direction changes, buffering the next intended direction.
|
|
pub struct Traverser {
|
|
/// The current position of the traverser in the graph.
|
|
pub position: Position,
|
|
/// The current direction of movement.
|
|
pub direction: Direction,
|
|
/// Buffered direction change with remaining frame count for timing.
|
|
///
|
|
/// The `u8` value represents the number of frames remaining before
|
|
/// the buffered direction expires. This allows for responsive controls
|
|
/// by storing direction changes for a limited time.
|
|
pub next_direction: Option<(Direction, u8)>,
|
|
}
|
|
|
|
impl Traverser {
|
|
/// Creates a new traverser starting at the given node ID.
|
|
///
|
|
/// The traverser will immediately attempt to start moving in the initial direction.
|
|
pub fn new(graph: &Graph, start_node: NodeId, initial_direction: Direction) -> Self {
|
|
let mut traverser = Traverser {
|
|
position: Position::AtNode(start_node),
|
|
direction: initial_direction,
|
|
next_direction: Some((initial_direction, 1)),
|
|
};
|
|
|
|
// This will kickstart the traverser into motion
|
|
traverser.advance(graph, 0.0);
|
|
|
|
traverser
|
|
}
|
|
|
|
/// Sets the next direction for the traverser to take.
|
|
///
|
|
/// The direction is buffered and will be applied at the next opportunity,
|
|
/// typically when the traverser reaches a new node. This allows for responsive
|
|
/// controls, as the new direction is stored for a limited time.
|
|
pub fn set_next_direction(&mut self, new_direction: Direction) {
|
|
if self.direction != new_direction {
|
|
self.next_direction = Some((new_direction, 30));
|
|
}
|
|
}
|
|
|
|
/// Advances the traverser along the graph by a specified distance.
|
|
///
|
|
/// This method updates the traverser's position based on its current state
|
|
/// and the distance to travel.
|
|
///
|
|
/// - If at a node, it checks for a buffered direction to start moving.
|
|
/// - If between nodes, it moves along the current edge.
|
|
/// - If it reaches a node, it attempts to transition to a new edge based on
|
|
/// the buffered direction or by continuing straight.
|
|
/// - If no valid move is possible, it stops at the node.
|
|
pub fn advance(&mut self, graph: &Graph, distance: f32) {
|
|
// Decrement the remaining frames for the next direction
|
|
if let Some((direction, remaining)) = self.next_direction {
|
|
if remaining > 0 {
|
|
self.next_direction = Some((direction, remaining - 1));
|
|
} else {
|
|
self.next_direction = None;
|
|
}
|
|
}
|
|
|
|
match self.position {
|
|
Position::AtNode(node_id) => {
|
|
// We're not moving, but a buffered direction is available.
|
|
if let Some((next_direction, _)) = self.next_direction {
|
|
if let Some(edge) = graph.find_edge_in_direction(node_id, next_direction) {
|
|
// Start moving in that direction
|
|
self.position = Position::BetweenNodes {
|
|
from: node_id,
|
|
to: edge.target,
|
|
traversed: distance.max(0.0),
|
|
};
|
|
self.direction = next_direction;
|
|
}
|
|
|
|
self.next_direction = None; // Consume the buffered direction regardless of whether we started moving with it
|
|
}
|
|
}
|
|
Position::BetweenNodes { from, to, traversed } => {
|
|
// There is no point in any of the next logic if we don't travel at all
|
|
if distance <= 0.0 {
|
|
return;
|
|
}
|
|
|
|
let edge = graph
|
|
.find_edge(from, to)
|
|
.expect("Inconsistent state: Traverser is on a non-existent edge.");
|
|
|
|
let new_traversed = traversed + distance;
|
|
|
|
if new_traversed < edge.distance {
|
|
// Still on the same edge, just update the distance.
|
|
self.position = Position::BetweenNodes {
|
|
from,
|
|
to,
|
|
traversed: new_traversed,
|
|
};
|
|
} else {
|
|
let overflow = new_traversed - edge.distance;
|
|
let mut moved = false;
|
|
|
|
// If we buffered a direction, try to find an edge in that direction
|
|
if let Some((next_dir, _)) = self.next_direction {
|
|
if let Some(edge) = graph.find_edge_in_direction(to, next_dir) {
|
|
self.position = Position::BetweenNodes {
|
|
from: to,
|
|
to: edge.target,
|
|
traversed: overflow,
|
|
};
|
|
|
|
self.direction = next_dir; // Remember our new direction
|
|
self.next_direction = None; // Consume the buffered direction
|
|
moved = true;
|
|
}
|
|
}
|
|
|
|
// If we didn't move, try to continue in the current direction
|
|
if !moved {
|
|
if let Some(edge) = graph.find_edge_in_direction(to, self.direction) {
|
|
self.position = Position::BetweenNodes {
|
|
from: to,
|
|
to: edge.target,
|
|
traversed: overflow,
|
|
};
|
|
} else {
|
|
self.position = Position::AtNode(to);
|
|
self.next_direction = None;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::entity::direction::Direction;
|
|
|
|
fn create_test_graph() -> Graph {
|
|
let mut graph = Graph::new();
|
|
let node1 = graph.add_node(Node {
|
|
position: glam::Vec2::new(0.0, 0.0),
|
|
});
|
|
let node2 = graph.add_node(Node {
|
|
position: glam::Vec2::new(16.0, 0.0),
|
|
});
|
|
let node3 = graph.add_node(Node {
|
|
position: glam::Vec2::new(0.0, 16.0),
|
|
});
|
|
|
|
graph.connect(node1, node2, false, None, Direction::Right).unwrap();
|
|
graph.connect(node1, node3, false, None, Direction::Down).unwrap();
|
|
|
|
graph
|
|
}
|
|
|
|
#[test]
|
|
fn test_graph_new() {
|
|
let graph = Graph::new();
|
|
assert_eq!(graph.node_count(), 0);
|
|
assert!(graph.adjacency_list.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_graph_add_node() {
|
|
let mut graph = Graph::new();
|
|
let node = Node {
|
|
position: glam::Vec2::new(10.0, 20.0),
|
|
};
|
|
let id = graph.add_node(node);
|
|
|
|
assert_eq!(id, 0);
|
|
assert_eq!(graph.node_count(), 1);
|
|
assert_eq!(graph.adjacency_list.len(), 1);
|
|
|
|
let retrieved_node = graph.get_node(id).unwrap();
|
|
assert_eq!(retrieved_node.position, glam::Vec2::new(10.0, 20.0));
|
|
}
|
|
|
|
#[test]
|
|
fn test_graph_node_count() {
|
|
let mut graph = Graph::new();
|
|
assert_eq!(graph.node_count(), 0);
|
|
|
|
graph.add_node(Node {
|
|
position: glam::Vec2::new(0.0, 0.0),
|
|
});
|
|
assert_eq!(graph.node_count(), 1);
|
|
|
|
graph.add_node(Node {
|
|
position: glam::Vec2::new(1.0, 1.0),
|
|
});
|
|
assert_eq!(graph.node_count(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_graph_get_node() {
|
|
let mut graph = Graph::new();
|
|
let node = Node {
|
|
position: glam::Vec2::new(5.0, 10.0),
|
|
};
|
|
let id = graph.add_node(node);
|
|
|
|
let retrieved = graph.get_node(id).unwrap();
|
|
assert_eq!(retrieved.position, glam::Vec2::new(5.0, 10.0));
|
|
|
|
assert!(graph.get_node(999).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_graph_connect() {
|
|
let mut graph = Graph::new();
|
|
let node1 = graph.add_node(Node {
|
|
position: glam::Vec2::new(0.0, 0.0),
|
|
});
|
|
let node2 = graph.add_node(Node {
|
|
position: glam::Vec2::new(16.0, 0.0),
|
|
});
|
|
|
|
let result = graph.connect(node1, node2, false, None, Direction::Right);
|
|
assert!(result.is_ok());
|
|
|
|
// Check that edges were added in both directions
|
|
let edge1 = graph.find_edge_in_direction(node1, Direction::Right);
|
|
let edge2 = graph.find_edge_in_direction(node2, Direction::Left);
|
|
|
|
assert!(edge1.is_some());
|
|
assert!(edge2.is_some());
|
|
assert_eq!(edge1.unwrap().target, node2);
|
|
assert_eq!(edge2.unwrap().target, node1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_graph_connect_invalid_nodes() {
|
|
let mut graph = Graph::new();
|
|
let node1 = graph.add_node(Node {
|
|
position: glam::Vec2::new(0.0, 0.0),
|
|
});
|
|
|
|
// Try to connect to non-existent node
|
|
let result = graph.connect(node1, 999, false, None, Direction::Right);
|
|
assert!(result.is_err());
|
|
|
|
// Try to connect from non-existent node
|
|
let result = graph.connect(999, node1, false, None, Direction::Right);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_graph_find_edge() {
|
|
let mut graph = Graph::new();
|
|
let node1 = graph.add_node(Node {
|
|
position: glam::Vec2::new(0.0, 0.0),
|
|
});
|
|
let node2 = graph.add_node(Node {
|
|
position: glam::Vec2::new(16.0, 0.0),
|
|
});
|
|
|
|
graph.connect(node1, node2, false, None, Direction::Right).unwrap();
|
|
|
|
let edge = graph.find_edge(node1, node2);
|
|
assert!(edge.is_some());
|
|
assert_eq!(edge.unwrap().target, node2);
|
|
|
|
// Test non-existent edge
|
|
assert!(graph.find_edge(node1, 999).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_graph_find_edge_in_direction() {
|
|
let mut graph = Graph::new();
|
|
let node1 = graph.add_node(Node {
|
|
position: glam::Vec2::new(0.0, 0.0),
|
|
});
|
|
let node2 = graph.add_node(Node {
|
|
position: glam::Vec2::new(16.0, 0.0),
|
|
});
|
|
|
|
graph.connect(node1, node2, false, None, Direction::Right).unwrap();
|
|
|
|
let edge = graph.find_edge_in_direction(node1, Direction::Right);
|
|
assert!(edge.is_some());
|
|
assert_eq!(edge.unwrap().target, node2);
|
|
|
|
// Test non-existent direction
|
|
assert!(graph.find_edge_in_direction(node1, Direction::Up).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_intersection_edges() {
|
|
let mut intersection = Intersection::default();
|
|
intersection.set(
|
|
Direction::Up,
|
|
Edge {
|
|
target: 1,
|
|
distance: 10.0,
|
|
direction: Direction::Up,
|
|
},
|
|
);
|
|
intersection.set(
|
|
Direction::Right,
|
|
Edge {
|
|
target: 2,
|
|
distance: 15.0,
|
|
direction: Direction::Right,
|
|
},
|
|
);
|
|
|
|
let edges: Vec<_> = intersection.edges().collect();
|
|
assert_eq!(edges.len(), 2);
|
|
|
|
let up_edge = edges.iter().find(|e| e.direction == Direction::Up).unwrap();
|
|
let right_edge = edges.iter().find(|e| e.direction == Direction::Right).unwrap();
|
|
|
|
assert_eq!(up_edge.target, 1);
|
|
assert_eq!(up_edge.distance, 10.0);
|
|
assert_eq!(right_edge.target, 2);
|
|
assert_eq!(right_edge.distance, 15.0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_intersection_get() {
|
|
let mut intersection = Intersection::default();
|
|
let edge = Edge {
|
|
target: 1,
|
|
distance: 10.0,
|
|
direction: Direction::Up,
|
|
};
|
|
intersection.set(Direction::Up, edge);
|
|
|
|
let retrieved = intersection.get(Direction::Up);
|
|
assert!(retrieved.is_some());
|
|
assert_eq!(retrieved.unwrap().target, 1);
|
|
|
|
assert!(intersection.get(Direction::Down).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_intersection_set() {
|
|
let mut intersection = Intersection::default();
|
|
let edge = Edge {
|
|
target: 1,
|
|
distance: 10.0,
|
|
direction: Direction::Left,
|
|
};
|
|
|
|
intersection.set(Direction::Left, edge);
|
|
let retrieved = intersection.get(Direction::Left);
|
|
assert!(retrieved.is_some());
|
|
assert_eq!(retrieved.unwrap().target, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_position_is_at_node() {
|
|
let pos = Position::AtNode(5);
|
|
assert!(pos.is_at_node());
|
|
|
|
let pos = Position::BetweenNodes {
|
|
from: 1,
|
|
to: 2,
|
|
traversed: 5.0,
|
|
};
|
|
assert!(!pos.is_at_node());
|
|
}
|
|
|
|
#[test]
|
|
fn test_position_from_node_id() {
|
|
let pos = Position::AtNode(5);
|
|
assert_eq!(pos.from_node_id(), 5);
|
|
|
|
let pos = Position::BetweenNodes {
|
|
from: 1,
|
|
to: 2,
|
|
traversed: 5.0,
|
|
};
|
|
assert_eq!(pos.from_node_id(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_position_to_node_id() {
|
|
let pos = Position::AtNode(5);
|
|
assert_eq!(pos.to_node_id(), None);
|
|
|
|
let pos = Position::BetweenNodes {
|
|
from: 1,
|
|
to: 2,
|
|
traversed: 5.0,
|
|
};
|
|
assert_eq!(pos.to_node_id(), Some(2));
|
|
}
|
|
|
|
#[test]
|
|
fn test_position_is_stopped() {
|
|
let pos = Position::AtNode(5);
|
|
assert!(pos.is_stopped());
|
|
|
|
let pos = Position::BetweenNodes {
|
|
from: 1,
|
|
to: 2,
|
|
traversed: 5.0,
|
|
};
|
|
assert!(!pos.is_stopped());
|
|
}
|
|
|
|
#[test]
|
|
fn test_traverser_new() {
|
|
let graph = create_test_graph();
|
|
let traverser = Traverser::new(&graph, 0, Direction::Left);
|
|
|
|
assert_eq!(traverser.direction, Direction::Left);
|
|
// The next_direction might be consumed immediately when the traverser starts moving
|
|
// So we just check that the direction is set correctly
|
|
assert_eq!(traverser.direction, Direction::Left);
|
|
}
|
|
|
|
#[test]
|
|
fn test_traverser_set_next_direction() {
|
|
let graph = create_test_graph();
|
|
let mut traverser = Traverser::new(&graph, 0, Direction::Left);
|
|
|
|
traverser.set_next_direction(Direction::Up);
|
|
assert!(traverser.next_direction.is_some());
|
|
assert_eq!(traverser.next_direction.unwrap().0, Direction::Up);
|
|
|
|
// Setting same direction should not change anything
|
|
traverser.set_next_direction(Direction::Up);
|
|
assert_eq!(traverser.next_direction.unwrap().0, Direction::Up);
|
|
}
|
|
|
|
#[test]
|
|
fn test_traverser_advance_at_node() {
|
|
let graph = create_test_graph();
|
|
let mut traverser = Traverser::new(&graph, 0, Direction::Right);
|
|
|
|
// Should start moving in the initial direction
|
|
traverser.advance(&graph, 5.0);
|
|
|
|
match traverser.position {
|
|
Position::BetweenNodes { from, to, traversed } => {
|
|
assert_eq!(from, 0);
|
|
assert_eq!(to, 1);
|
|
assert_eq!(traversed, 5.0);
|
|
}
|
|
_ => panic!("Expected to be between nodes"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_traverser_advance_between_nodes() {
|
|
let graph = create_test_graph();
|
|
let mut traverser = Traverser::new(&graph, 0, Direction::Right);
|
|
|
|
// Move to between nodes
|
|
traverser.advance(&graph, 5.0);
|
|
|
|
// Advance further
|
|
traverser.advance(&graph, 3.0);
|
|
|
|
match traverser.position {
|
|
Position::BetweenNodes { from, to, traversed } => {
|
|
assert_eq!(from, 0);
|
|
assert_eq!(to, 1);
|
|
assert_eq!(traversed, 8.0);
|
|
}
|
|
_ => panic!("Expected to be between nodes"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_edge_structure() {
|
|
let edge = Edge {
|
|
target: 5,
|
|
distance: 10.5,
|
|
direction: Direction::Up,
|
|
};
|
|
|
|
assert_eq!(edge.target, 5);
|
|
assert_eq!(edge.distance, 10.5);
|
|
assert_eq!(edge.direction, Direction::Up);
|
|
}
|
|
|
|
#[test]
|
|
fn test_node_structure() {
|
|
let node = Node {
|
|
position: glam::Vec2::new(10.0, 20.0),
|
|
};
|
|
|
|
assert_eq!(node.position, glam::Vec2::new(10.0, 20.0));
|
|
}
|
|
}
|