refactor: better graph connection functions & creation method, debug render connections

This commit is contained in:
2025-07-28 13:23:00 -05:00
parent 59aba9f691
commit d743aee393
2 changed files with 138 additions and 28 deletions

View File

@@ -1,5 +1,7 @@
use glam::Vec2; use glam::Vec2;
use crate::entity::direction::DIRECTIONS;
use super::direction::Direction; use super::direction::Direction;
/// A unique identifier for a node, represented by its index in the graph's storage. /// A unique identifier for a node, represented by its index in the graph's storage.
@@ -59,6 +61,11 @@ impl Intersection {
[self.up, self.down, self.left, self.right].into_iter().flatten() [self.up, self.down, self.left, self.right].into_iter().flatten()
} }
/// Returns an iterator over all directions that don't have an edge.
pub fn empty_directions(&self) -> impl Iterator<Item = Direction> + '_ {
DIRECTIONS.into_iter().filter(|dir| self.get(*dir).is_none())
}
/// Retrieves the edge in the specified direction, if it exists. /// Retrieves the edge in the specified direction, if it exists.
pub fn get(&self, direction: Direction) -> Option<Edge> { pub fn get(&self, direction: Direction) -> Option<Edge> {
match direction { match direction {
@@ -89,7 +96,7 @@ impl Intersection {
/// in an adjacency list, where each node has a list of outgoing edges. /// in an adjacency list, where each node has a list of outgoing edges.
pub struct Graph { pub struct Graph {
nodes: Vec<Node>, nodes: Vec<Node>,
adjacency_list: Vec<Intersection>, pub adjacency_list: Vec<Intersection>,
} }
impl Graph { impl Graph {
@@ -109,6 +116,31 @@ impl Graph {
id 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<(), &'static str> {
let to = self.add_node(new_node);
self.connect(from, to, None, direction)
}
/// Connects two existing nodes with an edge.
pub fn connect(&mut self, from: NodeId, to: NodeId, 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, distance, direction);
let edge_b = self.add_edge(to, from, 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. /// Adds a directed edge between two nodes.
/// ///
/// If `distance` is `None`, it will be calculated automatically based on the /// If `distance` is `None`, it will be calculated automatically based on the

View File

@@ -10,7 +10,7 @@ use sdl2::render::{Canvas, RenderTarget};
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
use tracing::info; use tracing::info;
use crate::entity::graph::{Graph, Node}; use crate::entity::graph::{Graph, Node, NodeId};
use crate::texture::text::TextTexture; use crate::texture::text::TextTexture;
/// The game map, responsible for holding the tile-based layout and the navigation graph. /// The game map, responsible for holding the tile-based layout and the navigation graph.
@@ -79,6 +79,9 @@ impl Map {
}; };
info!("House door node id: {house_door_node_id}"); info!("House door node id: {house_door_node_id}");
// Connect the house door node to nearby nodes
Self::connect_house_door(&mut graph, house_door_node_id, &map);
Map { current: map, graph } Map { current: map, graph }
} }
@@ -125,47 +128,123 @@ impl Map {
let node_id = graph.add_node(Node { position: pos }); let node_id = graph.add_node(Node { position: pos });
grid_to_node.insert(start_pos, node_id); grid_to_node.insert(start_pos, node_id);
while let Some(grid_pos) = queue.pop_front() { while let Some(source_position) = queue.pop_front() {
for &dir in DIRECTIONS.iter() { for &dir in DIRECTIONS.iter() {
let neighbor = grid_pos + dir.to_ivec2(); let new_position = source_position + dir.to_ivec2();
if neighbor.x < 0 if new_position.x < 0
|| neighbor.x >= BOARD_CELL_SIZE.x as i32 || new_position.x >= BOARD_CELL_SIZE.x as i32
|| neighbor.y < 0 || new_position.y < 0
|| neighbor.y >= BOARD_CELL_SIZE.y as i32 || new_position.y >= BOARD_CELL_SIZE.y as i32
{ {
continue; continue;
} }
if grid_to_node.contains_key(&neighbor) { if grid_to_node.contains_key(&new_position) {
continue; continue;
} }
if matches!( if matches!(
map[neighbor.x as usize][neighbor.y as usize], map[new_position.x as usize][new_position.y as usize],
MapTile::Pellet | MapTile::PowerPellet | MapTile::Empty | MapTile::Tunnel | MapTile::StartingPosition(_) MapTile::Pellet | MapTile::PowerPellet | MapTile::Empty | MapTile::Tunnel | MapTile::StartingPosition(_)
) { ) {
let pos = let pos = Vec2::new(
Vec2::new((neighbor.x * CELL_SIZE as i32) as f32, (neighbor.y * CELL_SIZE as i32) as f32) + cell_offset; (new_position.x * CELL_SIZE as i32) as f32,
let node_id = graph.add_node(Node { position: pos }); (new_position.y * CELL_SIZE as i32) as f32,
grid_to_node.insert(neighbor, node_id); ) + cell_offset;
queue.push_back(neighbor); let new_node_id = graph.add_node(Node { position: pos });
grid_to_node.insert(new_position, new_node_id);
queue.push_back(new_position);
// Connect the new node to the source node
let source_node_id = grid_to_node
.get(&source_position)
.expect(&format!("Source node not found for {source_position}"));
graph
.connect(*source_node_id, new_node_id, None, dir)
.expect("Failed to add edge");
} }
} }
} }
for (grid_pos, &node_id) in &grid_to_node { for (grid_pos, &node_id) in &grid_to_node {
for &dir in DIRECTIONS.iter() { for dir in DIRECTIONS {
let neighbor = grid_pos + dir.to_ivec2(); if graph.adjacency_list[node_id].get(dir).is_none() {
let neighbor = grid_pos + dir.to_ivec2();
if let Some(&neighbor_id) = grid_to_node.get(&neighbor) { if let Some(&neighbor_id) = grid_to_node.get(&neighbor) {
graph.add_edge(node_id, neighbor_id, None, dir).expect("Failed to add edge"); let _ = graph.connect(node_id, neighbor_id, None, dir);
}
} }
} }
} }
graph graph
} }
/// Connects the house door node to nearby walkable nodes in the graph.
///
/// This function finds nodes within a reasonable distance of the house door
/// and creates bidirectional connections to them.
fn connect_house_door(
graph: &mut Graph,
house_door_node_id: NodeId,
_map: &[[MapTile; BOARD_CELL_SIZE.y as usize]; BOARD_CELL_SIZE.x as usize],
) {
let house_position = graph.get_node(house_door_node_id).unwrap().position;
let connection_distance = CELL_SIZE as f32 * 1.5; // Connect to nodes within 1.5 cells
// Find all nodes that should be connected to the house door
for node_id in 0..graph.node_count() {
if node_id == house_door_node_id {
continue; // Skip the house door node itself
}
let node_position = graph.get_node(node_id).unwrap().position;
let distance = house_position.distance(node_position);
if distance <= connection_distance {
// Determine the direction from house door to this node
let direction = Self::direction_from_to(house_position, node_position);
// Add bidirectional connection
if let Err(e) = graph.add_edge(house_door_node_id, node_id, None, direction) {
info!("Failed to connect house door to node {}: {}", node_id, e);
}
// Add reverse connection
let reverse_direction = direction.opposite();
if let Err(e) = graph.add_edge(node_id, house_door_node_id, None, reverse_direction) {
info!("Failed to connect node {} to house door: {}", node_id, e);
}
}
}
}
/// Determines the primary direction from one position to another.
///
/// This is a simplified direction calculation that prioritizes the axis
/// with the larger difference.
fn direction_from_to(from: Vec2, to: Vec2) -> crate::entity::direction::Direction {
let diff = to - from;
let abs_x = diff.x.abs();
let abs_y = diff.y.abs();
if abs_x > abs_y {
if diff.x > 0.0 {
crate::entity::direction::Direction::Right
} else {
crate::entity::direction::Direction::Left
}
} else {
if diff.y > 0.0 {
crate::entity::direction::Direction::Down
} else {
crate::entity::direction::Direction::Up
}
}
}
/// Finds the starting position for a given entity ID. /// Finds the starting position for a given entity ID.
/// ///
/// # Arguments /// # Arguments
@@ -213,15 +292,14 @@ impl Map {
let pos = node.position + BOARD_PIXEL_OFFSET.as_vec2(); let pos = node.position + BOARD_PIXEL_OFFSET.as_vec2();
// Draw connections // Draw connections
// TODO: fix this canvas.set_draw_color(Color::BLUE);
// canvas.set_draw_color(Color::BLUE);
// for neighbor in node.neighbors() { for edge in self.graph.adjacency_list[i].edges() {
// let end_pos = neighbor.get(&self.node_map).position + BOARD_PIXEL_OFFSET.as_vec2(); let end_pos = self.graph.get_node(edge.target).unwrap().position + BOARD_PIXEL_OFFSET.as_vec2();
// canvas canvas
// .draw_line((pos.x as i32, pos.y as i32), (end_pos.x as i32, end_pos.y as i32)) .draw_line((pos.x as i32, pos.y as i32), (end_pos.x as i32, end_pos.y as i32))
// .unwrap(); .unwrap();
// } }
// Draw node // Draw node
// let color = if pacman.position.from_node_idx() == i.into() { // let color = if pacman.position.from_node_idx() == i.into() {