mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-06 05:15:49 -06:00
refactor: move graph traversal code into traversal.rs
This commit is contained in:
@@ -11,7 +11,8 @@ use tracing::debug;
|
|||||||
|
|
||||||
use crate::constants::BOARD_PIXEL_OFFSET;
|
use crate::constants::BOARD_PIXEL_OFFSET;
|
||||||
use crate::entity::direction::Direction;
|
use crate::entity::direction::Direction;
|
||||||
use crate::entity::graph::{Edge, EdgePermissions, Graph, NodeId, Position, Traverser};
|
use crate::entity::graph::{Edge, EdgePermissions, Graph, NodeId};
|
||||||
|
use crate::entity::traversal::{Position, Traverser};
|
||||||
use crate::helpers::centered_with_size;
|
use crate::helpers::centered_with_size;
|
||||||
use crate::texture::animated::AnimatedTexture;
|
use crate::texture::animated::AnimatedTexture;
|
||||||
use crate::texture::directional::DirectionalAnimatedTexture;
|
use crate::texture::directional::DirectionalAnimatedTexture;
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ impl Graph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Connects a new node to the graph and adds an edge between the existing node and the new node.
|
/// 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> {
|
pub fn add_connected(&mut self, from: NodeId, direction: Direction, new_node: Node) -> Result<NodeId, &'static str> {
|
||||||
let to = self.add_node(new_node);
|
let to = self.add_node(new_node);
|
||||||
self.connect(from, to, false, None, direction)?;
|
self.connect(from, to, false, None, direction)?;
|
||||||
Ok(to)
|
Ok(to)
|
||||||
@@ -236,208 +236,3 @@ impl Default for Graph {
|
|||||||
Self::new()
|
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<F>(graph: &Graph, start_node: NodeId, initial_direction: Direction, can_traverse: &F) -> Self
|
|
||||||
where
|
|
||||||
F: Fn(Edge) -> bool,
|
|
||||||
{
|
|
||||||
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, can_traverse);
|
|
||||||
|
|
||||||
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<F>(&mut self, graph: &Graph, distance: f32, can_traverse: &F)
|
|
||||||
where
|
|
||||||
F: Fn(Edge) -> bool,
|
|
||||||
{
|
|
||||||
// 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) {
|
|
||||||
if can_traverse(edge) {
|
|
||||||
// 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) {
|
|
||||||
if can_traverse(edge) {
|
|
||||||
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) {
|
|
||||||
if can_traverse(edge) {
|
|
||||||
self.position = Position::BetweenNodes {
|
|
||||||
from: to,
|
|
||||||
to: edge.target,
|
|
||||||
traversed: overflow,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
self.position = Position::AtNode(to);
|
|
||||||
self.next_direction = None;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.position = Position::AtNode(to);
|
|
||||||
self.next_direction = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ pub mod direction;
|
|||||||
pub mod ghost;
|
pub mod ghost;
|
||||||
pub mod graph;
|
pub mod graph;
|
||||||
pub mod pacman;
|
pub mod pacman;
|
||||||
|
pub mod traversal;
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ use glam::{UVec2, Vec2};
|
|||||||
|
|
||||||
use crate::constants::BOARD_PIXEL_OFFSET;
|
use crate::constants::BOARD_PIXEL_OFFSET;
|
||||||
use crate::entity::direction::Direction;
|
use crate::entity::direction::Direction;
|
||||||
use crate::entity::graph::{Edge, EdgePermissions, Graph, NodeId, Position, Traverser};
|
use crate::entity::graph::{Edge, EdgePermissions, Graph, NodeId};
|
||||||
|
use crate::entity::traversal::{Position, Traverser};
|
||||||
use crate::helpers::centered_with_size;
|
use crate::helpers::centered_with_size;
|
||||||
use crate::texture::animated::AnimatedTexture;
|
use crate::texture::animated::AnimatedTexture;
|
||||||
use crate::texture::directional::DirectionalAnimatedTexture;
|
use crate::texture::directional::DirectionalAnimatedTexture;
|
||||||
|
|||||||
205
src/entity/traversal.rs
Normal file
205
src/entity/traversal.rs
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
use super::direction::Direction;
|
||||||
|
use super::graph::{Edge, Graph, NodeId};
|
||||||
|
|
||||||
|
/// 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<F>(graph: &Graph, start_node: NodeId, initial_direction: Direction, can_traverse: &F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(Edge) -> bool,
|
||||||
|
{
|
||||||
|
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, can_traverse);
|
||||||
|
|
||||||
|
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<F>(&mut self, graph: &Graph, distance: f32, can_traverse: &F)
|
||||||
|
where
|
||||||
|
F: Fn(Edge) -> bool,
|
||||||
|
{
|
||||||
|
// 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) {
|
||||||
|
if can_traverse(edge) {
|
||||||
|
// 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) {
|
||||||
|
if can_traverse(edge) {
|
||||||
|
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) {
|
||||||
|
if can_traverse(edge) {
|
||||||
|
self.position = Position::BetweenNodes {
|
||||||
|
from: to,
|
||||||
|
to: edge.target,
|
||||||
|
traversed: overflow,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
self.position = Position::AtNode(to);
|
||||||
|
self.next_direction = None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.position = Position::AtNode(to);
|
||||||
|
self.next_direction = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -331,7 +331,7 @@ impl Map {
|
|||||||
.expect("Left tunnel entrance node not found");
|
.expect("Left tunnel entrance node not found");
|
||||||
|
|
||||||
graph
|
graph
|
||||||
.connect_node(
|
.add_connected(
|
||||||
left_tunnel_entrance_node_id,
|
left_tunnel_entrance_node_id,
|
||||||
Direction::Left,
|
Direction::Left,
|
||||||
Node {
|
Node {
|
||||||
@@ -350,7 +350,7 @@ impl Map {
|
|||||||
.expect("Right tunnel entrance node not found");
|
.expect("Right tunnel entrance node not found");
|
||||||
|
|
||||||
graph
|
graph
|
||||||
.connect_node(
|
.add_connected(
|
||||||
right_tunnel_entrance_node_id,
|
right_tunnel_entrance_node_id,
|
||||||
Direction::Right,
|
Direction::Right,
|
||||||
Node {
|
Node {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use pacman::entity::direction::Direction;
|
use pacman::entity::direction::Direction;
|
||||||
use pacman::entity::graph::{EdgePermissions, Graph, Node, Position, Traverser};
|
use pacman::entity::graph::{EdgePermissions, Graph, Node};
|
||||||
|
use pacman::entity::traversal::{Position, Traverser};
|
||||||
|
|
||||||
fn create_test_graph() -> Graph {
|
fn create_test_graph() -> Graph {
|
||||||
let mut graph = Graph::new();
|
let mut graph = Graph::new();
|
||||||
|
|||||||
Reference in New Issue
Block a user