mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-10 08:07:56 -06:00
refactor: switch NodeId to u16, use I8Vec2 for grid coordinates
This commit is contained in:
@@ -11,7 +11,7 @@ use crate::systems::blinking::Blinking;
|
|||||||
use crate::systems::movement::{BufferedDirection, Position, Velocity};
|
use crate::systems::movement::{BufferedDirection, Position, Velocity};
|
||||||
use crate::systems::profiling::SystemId;
|
use crate::systems::profiling::SystemId;
|
||||||
use crate::systems::render::RenderDirty;
|
use crate::systems::render::RenderDirty;
|
||||||
use crate::systems::{self, ghost_collision_system, present_system, Hidden, MovementModifiers};
|
use crate::systems::{self, ghost_collision_system, present_system, Hidden, MovementModifiers, NodeId};
|
||||||
use crate::systems::{
|
use crate::systems::{
|
||||||
audio_system, blinking_system, collision_system, debug_render_system, directional_render_system, dirty_render_system,
|
audio_system, blinking_system, collision_system, debug_render_system, directional_render_system, dirty_render_system,
|
||||||
eaten_ghost_system, ghost_movement_system, ghost_state_animation_system, hud_render_system, item_system, profile,
|
eaten_ghost_system, ghost_movement_system, ghost_state_animation_system, hud_render_system, item_system, profile,
|
||||||
@@ -303,7 +303,7 @@ impl Game {
|
|||||||
.ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound("maze/energizer.png".to_string())))?;
|
.ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound("maze/energizer.png".to_string())))?;
|
||||||
|
|
||||||
// Build a list of item entities to spawn from the map
|
// Build a list of item entities to spawn from the map
|
||||||
let nodes: Vec<(usize, EntityType, AtlasTile, f32)> = world
|
let nodes: Vec<(NodeId, EntityType, AtlasTile, f32)> = world
|
||||||
.resource::<Map>()
|
.resource::<Map>()
|
||||||
.iter_nodes()
|
.iter_nodes()
|
||||||
.filter_map(|(id, tile)| match tile {
|
.filter_map(|(id, tile)| match tile {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use crate::map::graph::{Graph, Node, TraversalFlags};
|
|||||||
use crate::map::parser::MapTileParser;
|
use crate::map::parser::MapTileParser;
|
||||||
use crate::systems::movement::NodeId;
|
use crate::systems::movement::NodeId;
|
||||||
use bevy_ecs::resource::Resource;
|
use bevy_ecs::resource::Resource;
|
||||||
use glam::{IVec2, Vec2};
|
use glam::{I8Vec2, IVec2, Vec2};
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ pub struct Map {
|
|||||||
/// Connected graph of navigable positions.
|
/// Connected graph of navigable positions.
|
||||||
pub graph: Graph,
|
pub graph: Graph,
|
||||||
/// Bidirectional mapping between 2D grid coordinates and graph node indices.
|
/// Bidirectional mapping between 2D grid coordinates and graph node indices.
|
||||||
pub grid_to_node: HashMap<IVec2, NodeId>,
|
pub grid_to_node: HashMap<I8Vec2, NodeId>,
|
||||||
/// Predetermined spawn locations for all game entities
|
/// Predetermined spawn locations for all game entities
|
||||||
pub start_positions: NodePositions,
|
pub start_positions: NodePositions,
|
||||||
/// 2D array of tile types for collision detection and rendering
|
/// 2D array of tile types for collision detection and rendering
|
||||||
@@ -76,8 +76,8 @@ impl Map {
|
|||||||
let mut queue = VecDeque::new();
|
let mut queue = VecDeque::new();
|
||||||
queue.push_back(start_pos);
|
queue.push_back(start_pos);
|
||||||
let pos = Vec2::new(
|
let pos = Vec2::new(
|
||||||
(start_pos.x * CELL_SIZE as i32) as f32,
|
(start_pos.x as i32 * CELL_SIZE as i32) as f32,
|
||||||
(start_pos.y * CELL_SIZE as i32) as f32,
|
(start_pos.y as i32 * CELL_SIZE as i32) as f32,
|
||||||
) + cell_offset;
|
) + cell_offset;
|
||||||
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);
|
||||||
@@ -89,9 +89,9 @@ impl Map {
|
|||||||
|
|
||||||
// Skip if the new position is out of bounds
|
// Skip if the new position is out of bounds
|
||||||
if new_position.x < 0
|
if new_position.x < 0
|
||||||
|| new_position.x >= BOARD_CELL_SIZE.x as i32
|
|| new_position.x as i32 >= BOARD_CELL_SIZE.x as i32
|
||||||
|| new_position.y < 0
|
|| new_position.y < 0
|
||||||
|| new_position.y >= BOARD_CELL_SIZE.y as i32
|
|| new_position.y as i32 >= BOARD_CELL_SIZE.y as i32
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -108,8 +108,8 @@ impl Map {
|
|||||||
) {
|
) {
|
||||||
// Add the new position to the graph/queue
|
// Add the new position to the graph/queue
|
||||||
let pos = Vec2::new(
|
let pos = Vec2::new(
|
||||||
(new_position.x * CELL_SIZE as i32) as f32,
|
(new_position.x as i32 * CELL_SIZE as i32) as f32,
|
||||||
(new_position.y * CELL_SIZE as i32) as f32,
|
(new_position.y as i32 * CELL_SIZE as i32) as f32,
|
||||||
) + cell_offset;
|
) + cell_offset;
|
||||||
let new_node_id = graph.add_node(Node { position: pos });
|
let new_node_id = graph.add_node(Node { position: pos });
|
||||||
grid_to_node.insert(new_position, new_node_id);
|
grid_to_node.insert(new_position, new_node_id);
|
||||||
@@ -132,7 +132,7 @@ impl Map {
|
|||||||
for (grid_pos, &node_id) in &grid_to_node {
|
for (grid_pos, &node_id) in &grid_to_node {
|
||||||
for dir in Direction::DIRECTIONS {
|
for dir in Direction::DIRECTIONS {
|
||||||
// If the node doesn't have an edge in this direction, look for a neighbor in that direction
|
// If the node doesn't have an edge in this direction, look for a neighbor in that direction
|
||||||
if graph.adjacency_list[node_id].get(dir).is_none() {
|
if graph.adjacency_list[node_id as usize].get(dir).is_none() {
|
||||||
let neighbor = grid_pos + dir.as_ivec2();
|
let neighbor = grid_pos + dir.as_ivec2();
|
||||||
// If the neighbor exists, connect the node to it
|
// If the neighbor exists, connect the node to it
|
||||||
if let Some(&neighbor_id) = grid_to_node.get(&neighbor) {
|
if let Some(&neighbor_id) = grid_to_node.get(&neighbor) {
|
||||||
@@ -199,9 +199,9 @@ impl Map {
|
|||||||
/// representing the four key positions within the ghost house structure.
|
/// representing the four key positions within the ghost house structure.
|
||||||
fn build_house(
|
fn build_house(
|
||||||
graph: &mut Graph,
|
graph: &mut Graph,
|
||||||
grid_to_node: &HashMap<IVec2, NodeId>,
|
grid_to_node: &HashMap<I8Vec2, NodeId>,
|
||||||
house_door: &[Option<IVec2>; 2],
|
house_door: &[Option<I8Vec2>; 2],
|
||||||
) -> GameResult<(usize, usize, usize, usize)> {
|
) -> GameResult<(NodeId, NodeId, NodeId, NodeId)> {
|
||||||
// Calculate the position of the house entrance node
|
// Calculate the position of the house entrance node
|
||||||
let (house_entrance_node_id, house_entrance_node_position) = {
|
let (house_entrance_node_id, house_entrance_node_position) = {
|
||||||
// Translate the grid positions to the actual node ids
|
// Translate the grid positions to the actual node ids
|
||||||
@@ -222,10 +222,13 @@ impl Map {
|
|||||||
|
|
||||||
// Calculate the position of the house node
|
// Calculate the position of the house node
|
||||||
let (node_id, node_position) = {
|
let (node_id, node_position) = {
|
||||||
let left_pos = graph.get_node(*left_node).ok_or(MapError::NodeNotFound(*left_node))?.position;
|
let left_pos = graph
|
||||||
|
.get_node(*left_node)
|
||||||
|
.ok_or(MapError::NodeNotFound(*left_node as usize))?
|
||||||
|
.position;
|
||||||
let right_pos = graph
|
let right_pos = graph
|
||||||
.get_node(*right_node)
|
.get_node(*right_node)
|
||||||
.ok_or(MapError::NodeNotFound(*right_node))?
|
.ok_or(MapError::NodeNotFound(*right_node as usize))?
|
||||||
.position;
|
.position;
|
||||||
let house_node = graph.add_node(Node {
|
let house_node = graph.add_node(Node {
|
||||||
position: left_pos.lerp(right_pos, 0.5),
|
position: left_pos.lerp(right_pos, 0.5),
|
||||||
@@ -249,10 +252,10 @@ impl Map {
|
|||||||
// Place the nodes at, above, and below the center position
|
// Place the nodes at, above, and below the center position
|
||||||
let center_node_id = graph.add_node(Node { position: center_pos });
|
let center_node_id = graph.add_node(Node { position: center_pos });
|
||||||
let top_node_id = graph.add_node(Node {
|
let top_node_id = graph.add_node(Node {
|
||||||
position: center_pos + (Direction::Up.as_ivec2() * (CELL_SIZE as i32 / 2)).as_vec2(),
|
position: center_pos + IVec2::from(Direction::Up.as_ivec2()).as_vec2() * (CELL_SIZE as f32 / 2.0),
|
||||||
});
|
});
|
||||||
let bottom_node_id = graph.add_node(Node {
|
let bottom_node_id = graph.add_node(Node {
|
||||||
position: center_pos + (Direction::Down.as_ivec2() * (CELL_SIZE as i32 / 2)).as_vec2(),
|
position: center_pos + IVec2::from(Direction::Down.as_ivec2()).as_vec2() * (CELL_SIZE as f32 / 2.0),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Connect the center node to the top and bottom nodes
|
// Connect the center node to the top and bottom nodes
|
||||||
@@ -268,7 +271,7 @@ impl Map {
|
|||||||
|
|
||||||
// Calculate the position of the center line's center node
|
// Calculate the position of the center line's center node
|
||||||
let center_line_center_position =
|
let center_line_center_position =
|
||||||
house_entrance_node_position + (Direction::Down.as_ivec2() * (3 * CELL_SIZE as i32)).as_vec2();
|
house_entrance_node_position + IVec2::from(Direction::Down.as_ivec2()).as_vec2() * (3.0 * CELL_SIZE as f32);
|
||||||
|
|
||||||
// Create the center line
|
// Create the center line
|
||||||
let (center_center_node_id, center_top_node_id) = create_house_line(graph, center_line_center_position)?;
|
let (center_center_node_id, center_top_node_id) = create_house_line(graph, center_line_center_position)?;
|
||||||
@@ -300,13 +303,13 @@ impl Map {
|
|||||||
// Create the left line
|
// Create the left line
|
||||||
let (left_center_node_id, _) = create_house_line(
|
let (left_center_node_id, _) = create_house_line(
|
||||||
graph,
|
graph,
|
||||||
center_line_center_position + (Direction::Left.as_ivec2() * (CELL_SIZE as i32 * 2)).as_vec2(),
|
center_line_center_position + IVec2::from(Direction::Left.as_ivec2()).as_vec2() * (CELL_SIZE as f32 * 2.0),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Create the right line
|
// Create the right line
|
||||||
let (right_center_node_id, _) = create_house_line(
|
let (right_center_node_id, _) = create_house_line(
|
||||||
graph,
|
graph,
|
||||||
center_line_center_position + (Direction::Right.as_ivec2() * (CELL_SIZE as i32 * 2)).as_vec2(),
|
center_line_center_position + IVec2::from(Direction::Right.as_ivec2()).as_vec2() * (CELL_SIZE as f32 * 2.0),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
debug!("Left center node id: {left_center_node_id}");
|
debug!("Left center node id: {left_center_node_id}");
|
||||||
@@ -336,8 +339,8 @@ impl Map {
|
|||||||
/// Creates hidden intermediate nodes beyond the visible tunnel entrances and connects them with zero-distance edges for instantaneous traversal.
|
/// Creates hidden intermediate nodes beyond the visible tunnel entrances and connects them with zero-distance edges for instantaneous traversal.
|
||||||
fn build_tunnels(
|
fn build_tunnels(
|
||||||
graph: &mut Graph,
|
graph: &mut Graph,
|
||||||
grid_to_node: &HashMap<IVec2, NodeId>,
|
grid_to_node: &HashMap<I8Vec2, NodeId>,
|
||||||
tunnel_ends: &[Option<IVec2>; 2],
|
tunnel_ends: &[Option<I8Vec2>; 2],
|
||||||
) -> GameResult<()> {
|
) -> GameResult<()> {
|
||||||
// Create the hidden tunnel nodes
|
// Create the hidden tunnel nodes
|
||||||
let left_tunnel_hidden_node_id = {
|
let left_tunnel_hidden_node_id = {
|
||||||
@@ -353,7 +356,7 @@ impl Map {
|
|||||||
Direction::Left,
|
Direction::Left,
|
||||||
Node {
|
Node {
|
||||||
position: left_tunnel_entrance_node.position
|
position: left_tunnel_entrance_node.position
|
||||||
+ (Direction::Left.as_ivec2() * (CELL_SIZE as i32 * 2)).as_vec2(),
|
+ IVec2::from(Direction::Left.as_ivec2()).as_vec2() * (CELL_SIZE as f32 * 2.0),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
@@ -378,7 +381,7 @@ impl Map {
|
|||||||
Direction::Right,
|
Direction::Right,
|
||||||
Node {
|
Node {
|
||||||
position: right_tunnel_entrance_node.position
|
position: right_tunnel_entrance_node.position
|
||||||
+ (Direction::Right.as_ivec2() * (CELL_SIZE as i32 * 2)).as_vec2(),
|
+ IVec2::from(Direction::Right.as_ivec2()).as_vec2() * (CELL_SIZE as f32 * 2.0),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use glam::IVec2;
|
use glam::I8Vec2;
|
||||||
use strum_macros::AsRefStr;
|
use strum_macros::AsRefStr;
|
||||||
|
|
||||||
/// The four cardinal directions.
|
/// The four cardinal directions.
|
||||||
@@ -28,8 +28,8 @@ impl Direction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the direction as an IVec2.
|
/// Returns the direction as an I8Vec2.
|
||||||
pub fn as_ivec2(self) -> IVec2 {
|
pub fn as_ivec2(self) -> I8Vec2 {
|
||||||
self.into()
|
self.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,13 +45,13 @@ impl Direction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Direction> for IVec2 {
|
impl From<Direction> for I8Vec2 {
|
||||||
fn from(dir: Direction) -> Self {
|
fn from(dir: Direction) -> Self {
|
||||||
match dir {
|
match dir {
|
||||||
Direction::Up => -IVec2::Y,
|
Direction::Up => -I8Vec2::Y,
|
||||||
Direction::Down => IVec2::Y,
|
Direction::Down => I8Vec2::Y,
|
||||||
Direction::Left => -IVec2::X,
|
Direction::Left => -I8Vec2::X,
|
||||||
Direction::Right => IVec2::X,
|
Direction::Right => I8Vec2::X,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ impl Graph {
|
|||||||
|
|
||||||
/// Adds a new node with the given data to the graph and returns its ID.
|
/// Adds a new node with the given data to the graph and returns its ID.
|
||||||
pub fn add_node(&mut self, data: Node) -> NodeId {
|
pub fn add_node(&mut self, data: Node) -> NodeId {
|
||||||
let id = self.nodes.len();
|
let id = self.nodes.len() as NodeId;
|
||||||
self.nodes.push(data);
|
self.nodes.push(data);
|
||||||
self.adjacency_list.push(Intersection::default());
|
self.adjacency_list.push(Intersection::default());
|
||||||
id
|
id
|
||||||
@@ -129,10 +129,10 @@ impl Graph {
|
|||||||
distance: Option<f32>,
|
distance: Option<f32>,
|
||||||
direction: Direction,
|
direction: Direction,
|
||||||
) -> Result<(), &'static str> {
|
) -> Result<(), &'static str> {
|
||||||
if from >= self.adjacency_list.len() {
|
if from as usize >= self.adjacency_list.len() {
|
||||||
return Err("From node does not exist.");
|
return Err("From node does not exist.");
|
||||||
}
|
}
|
||||||
if to >= self.adjacency_list.len() {
|
if to as usize >= self.adjacency_list.len() {
|
||||||
return Err("To node does not exist.");
|
return Err("To node does not exist.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,8 +178,8 @@ impl Graph {
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// If no distance is provided, calculate it based on the positions of the nodes
|
// If no distance is provided, calculate it based on the positions of the nodes
|
||||||
let from_pos = self.nodes[from].position;
|
let from_pos = self.nodes[from as usize].position;
|
||||||
let to_pos = self.nodes[to].position;
|
let to_pos = self.nodes[to as usize].position;
|
||||||
from_pos.distance(to_pos)
|
from_pos.distance(to_pos)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -187,11 +187,11 @@ impl Graph {
|
|||||||
traversal_flags,
|
traversal_flags,
|
||||||
};
|
};
|
||||||
|
|
||||||
if from >= self.adjacency_list.len() {
|
if from as usize >= self.adjacency_list.len() {
|
||||||
return Err("From node does not exist.");
|
return Err("From node does not exist.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let adjacency_list = &mut self.adjacency_list[from];
|
let adjacency_list = &mut self.adjacency_list[from as usize];
|
||||||
|
|
||||||
// Check if the edge already exists in this direction or to the same target
|
// 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 let Some(err) = adjacency_list.edges().find_map(|e| {
|
||||||
@@ -215,7 +215,7 @@ impl Graph {
|
|||||||
|
|
||||||
/// Retrieves an immutable reference to a node's data.
|
/// Retrieves an immutable reference to a node's data.
|
||||||
pub fn get_node(&self, id: NodeId) -> Option<&Node> {
|
pub fn get_node(&self, id: NodeId) -> Option<&Node> {
|
||||||
self.nodes.get(id)
|
self.nodes.get(id as usize)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator over all nodes in the graph.
|
/// Returns an iterator over all nodes in the graph.
|
||||||
@@ -228,17 +228,17 @@ impl Graph {
|
|||||||
self.adjacency_list
|
self.adjacency_list
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.flat_map(|(node_id, intersection)| intersection.edges().map(move |edge| (node_id, edge)))
|
.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.
|
/// Finds a specific edge from a source node to a target node.
|
||||||
pub fn find_edge(&self, from: NodeId, to: NodeId) -> Option<Edge> {
|
pub fn find_edge(&self, from: NodeId, to: NodeId) -> Option<Edge> {
|
||||||
self.adjacency_list.get(from)?.edges().find(|edge| edge.target == to)
|
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.
|
/// 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> {
|
pub fn find_edge_in_direction(&self, from: NodeId, direction: Direction) -> Option<Edge> {
|
||||||
self.adjacency_list.get(from)?.get(direction)
|
self.adjacency_list.get(from as usize)?.get(direction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use crate::constants::{MapTile, BOARD_CELL_SIZE};
|
use crate::constants::{MapTile, BOARD_CELL_SIZE};
|
||||||
use crate::error::ParseError;
|
use crate::error::ParseError;
|
||||||
use glam::IVec2;
|
use glam::I8Vec2;
|
||||||
|
|
||||||
/// Structured representation of parsed ASCII board layout with extracted special positions.
|
/// Structured representation of parsed ASCII board layout with extracted special positions.
|
||||||
///
|
///
|
||||||
@@ -15,11 +15,11 @@ pub struct ParsedMap {
|
|||||||
/// 2D array of tiles converted from ASCII characters
|
/// 2D array of tiles converted from ASCII characters
|
||||||
pub tiles: [[MapTile; BOARD_CELL_SIZE.y as usize]; BOARD_CELL_SIZE.x as usize],
|
pub tiles: [[MapTile; BOARD_CELL_SIZE.y as usize]; BOARD_CELL_SIZE.x as usize],
|
||||||
/// Two positions marking the ghost house entrance (represented by '=' characters)
|
/// Two positions marking the ghost house entrance (represented by '=' characters)
|
||||||
pub house_door: [Option<IVec2>; 2],
|
pub house_door: [Option<I8Vec2>; 2],
|
||||||
/// Two positions marking tunnel portals for wraparound teleportation ('T' characters)
|
/// Two positions marking tunnel portals for wraparound teleportation ('T' characters)
|
||||||
pub tunnel_ends: [Option<IVec2>; 2],
|
pub tunnel_ends: [Option<I8Vec2>; 2],
|
||||||
/// Starting position for Pac-Man (marked by 'X' character in the layout)
|
/// Starting position for Pac-Man (marked by 'X' character in the layout)
|
||||||
pub pacman_start: Option<IVec2>,
|
pub pacman_start: Option<I8Vec2>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parser for converting raw board layouts into structured map data.
|
/// Parser for converting raw board layouts into structured map data.
|
||||||
@@ -88,7 +88,7 @@ impl MapTileParser {
|
|||||||
let mut tiles = [[MapTile::Empty; BOARD_CELL_SIZE.y as usize]; BOARD_CELL_SIZE.x as usize];
|
let mut tiles = [[MapTile::Empty; BOARD_CELL_SIZE.y as usize]; BOARD_CELL_SIZE.x as usize];
|
||||||
let mut house_door = [None; 2];
|
let mut house_door = [None; 2];
|
||||||
let mut tunnel_ends = [None; 2];
|
let mut tunnel_ends = [None; 2];
|
||||||
let mut pacman_start: Option<IVec2> = None;
|
let mut pacman_start: Option<I8Vec2> = None;
|
||||||
|
|
||||||
for (y, line) in raw_board.iter().enumerate().take(BOARD_CELL_SIZE.y as usize) {
|
for (y, line) in raw_board.iter().enumerate().take(BOARD_CELL_SIZE.y as usize) {
|
||||||
for (x, character) in line.chars().enumerate().take(BOARD_CELL_SIZE.x as usize) {
|
for (x, character) in line.chars().enumerate().take(BOARD_CELL_SIZE.x as usize) {
|
||||||
@@ -98,16 +98,16 @@ impl MapTileParser {
|
|||||||
match tile {
|
match tile {
|
||||||
MapTile::Tunnel => {
|
MapTile::Tunnel => {
|
||||||
if tunnel_ends[0].is_none() {
|
if tunnel_ends[0].is_none() {
|
||||||
tunnel_ends[0] = Some(IVec2::new(x as i32, y as i32));
|
tunnel_ends[0] = Some(I8Vec2::new(x as i8, y as i8));
|
||||||
} else {
|
} else {
|
||||||
tunnel_ends[1] = Some(IVec2::new(x as i32, y as i32));
|
tunnel_ends[1] = Some(I8Vec2::new(x as i8, y as i8));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MapTile::Wall if character == '=' => {
|
MapTile::Wall if character == '=' => {
|
||||||
if house_door[0].is_none() {
|
if house_door[0].is_none() {
|
||||||
house_door[0] = Some(IVec2::new(x as i32, y as i32));
|
house_door[0] = Some(I8Vec2::new(x as i8, y as i8));
|
||||||
} else {
|
} else {
|
||||||
house_door[1] = Some(IVec2::new(x as i32, y as i32));
|
house_door[1] = Some(I8Vec2::new(x as i8, y as i8));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -115,7 +115,7 @@ impl MapTileParser {
|
|||||||
|
|
||||||
// Track Pac-Man's starting position
|
// Track Pac-Man's starting position
|
||||||
if character == 'X' {
|
if character == 'X' {
|
||||||
pacman_start = Some(IVec2::new(x as i32, y as i32));
|
pacman_start = Some(I8Vec2::new(x as i8, y as i8));
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles[x][y] = tile;
|
tiles[x][y] = tile;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::cmp::Ordering;
|
|||||||
|
|
||||||
use crate::constants::BOARD_PIXEL_OFFSET;
|
use crate::constants::BOARD_PIXEL_OFFSET;
|
||||||
use crate::map::builder::Map;
|
use crate::map::builder::Map;
|
||||||
use crate::systems::{Collider, CursorPosition, Position, SystemTimings};
|
use crate::systems::{Collider, CursorPosition, NodeId, Position, SystemTimings};
|
||||||
use bevy_ecs::resource::Resource;
|
use bevy_ecs::resource::Resource;
|
||||||
use bevy_ecs::system::{NonSendMut, Query, Res};
|
use bevy_ecs::system::{NonSendMut, Query, Res};
|
||||||
use glam::{IVec2, UVec2, Vec2};
|
use glam::{IVec2, UVec2, Vec2};
|
||||||
@@ -185,7 +185,7 @@ pub fn debug_render_system(
|
|||||||
|
|
||||||
// Render node ID if a node is highlighted
|
// Render node ID if a node is highlighted
|
||||||
if let Some(closest_node_id) = closest_node {
|
if let Some(closest_node_id) = closest_node {
|
||||||
let node = map.graph.get_node(closest_node_id).unwrap();
|
let node = map.graph.get_node(closest_node_id as NodeId).unwrap();
|
||||||
let pos = transform_position_with_offset(node.position, scale);
|
let pos = transform_position_with_offset(node.position, scale);
|
||||||
|
|
||||||
let surface = font
|
let surface = font
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ pub fn ghost_movement_system(
|
|||||||
loop {
|
loop {
|
||||||
match *position {
|
match *position {
|
||||||
Position::Stopped { node: current_node } => {
|
Position::Stopped { node: current_node } => {
|
||||||
let intersection = &map.graph.adjacency_list[current_node];
|
let intersection = &map.graph.adjacency_list[current_node as usize];
|
||||||
let opposite = velocity.direction.opposite();
|
let opposite = velocity.direction.opposite();
|
||||||
|
|
||||||
let mut non_opposite_options: SmallVec<[Edge; 3]> = SmallVec::new();
|
let mut non_opposite_options: SmallVec<[Edge; 3]> = SmallVec::new();
|
||||||
@@ -159,8 +159,11 @@ pub fn eaten_ghost_system(
|
|||||||
velocity.direction = direction;
|
velocity.direction = direction;
|
||||||
*position = Position::Moving {
|
*position = Position::Moving {
|
||||||
from: current_node,
|
from: current_node,
|
||||||
to: map.graph.adjacency_list[current_node].get(direction).unwrap().target,
|
to: map.graph.adjacency_list[current_node as usize].get(direction).unwrap().target,
|
||||||
remaining_distance: map.graph.adjacency_list[current_node].get(direction).unwrap().distance,
|
remaining_distance: map.graph.adjacency_list[current_node as usize]
|
||||||
|
.get(direction)
|
||||||
|
.unwrap()
|
||||||
|
.distance,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,8 +189,8 @@ pub fn eaten_ghost_system(
|
|||||||
velocity.direction = next_direction;
|
velocity.direction = next_direction;
|
||||||
*position = Position::Moving {
|
*position = Position::Moving {
|
||||||
from: to,
|
from: to,
|
||||||
to: map.graph.adjacency_list[to].get(next_direction).unwrap().target,
|
to: map.graph.adjacency_list[to as usize].get(next_direction).unwrap().target,
|
||||||
remaining_distance: map.graph.adjacency_list[to].get(next_direction).unwrap().distance,
|
remaining_distance: map.graph.adjacency_list[to as usize].get(next_direction).unwrap().distance,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,7 +205,11 @@ pub fn eaten_ghost_system(
|
|||||||
|
|
||||||
/// Helper function to find the direction from a node towards a target node.
|
/// Helper function to find the direction from a node towards a target node.
|
||||||
/// Uses simple greedy pathfinding - prefers straight lines when possible.
|
/// Uses simple greedy pathfinding - prefers straight lines when possible.
|
||||||
fn find_direction_to_target(map: &Map, from_node: usize, target_node: usize) -> Option<Direction> {
|
fn find_direction_to_target(
|
||||||
|
map: &Map,
|
||||||
|
from_node: crate::systems::movement::NodeId,
|
||||||
|
target_node: crate::systems::movement::NodeId,
|
||||||
|
) -> Option<Direction> {
|
||||||
let from_pos = map.graph.get_node(from_node).unwrap().position;
|
let from_pos = map.graph.get_node(from_node).unwrap().position;
|
||||||
let target_pos = map.graph.get_node(target_node).unwrap().position;
|
let target_pos = map.graph.get_node(target_node).unwrap().position;
|
||||||
|
|
||||||
@@ -224,7 +231,7 @@ fn find_direction_to_target(map: &Map, from_node: usize, target_node: usize) ->
|
|||||||
|
|
||||||
// Return first available direction towards target
|
// Return first available direction towards target
|
||||||
for direction in preferred_dirs {
|
for direction in preferred_dirs {
|
||||||
if let Some(edge) = map.graph.adjacency_list[from_node].get(direction) {
|
if let Some(edge) = map.graph.adjacency_list[from_node as usize].get(direction) {
|
||||||
if edge.traversal_flags.contains(TraversalFlags::GHOST) {
|
if edge.traversal_flags.contains(TraversalFlags::GHOST) {
|
||||||
return Some(direction);
|
return Some(direction);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use glam::Vec2;
|
|||||||
///
|
///
|
||||||
/// Nodes represent discrete movement targets in the maze. The index directly corresponds to the node's position in the
|
/// Nodes represent discrete movement targets in the maze. The index directly corresponds to the node's position in the
|
||||||
/// graph's internal storage arrays.
|
/// graph's internal storage arrays.
|
||||||
pub type NodeId = usize;
|
pub type NodeId = u16;
|
||||||
|
|
||||||
/// A component that represents the speed and cardinal direction of an entity.
|
/// A component that represents the speed and cardinal direction of an entity.
|
||||||
/// Speed is static, only applied when the entity has an edge to traverse.
|
/// Speed is static, only applied when the entity has an edge to traverse.
|
||||||
@@ -57,7 +57,7 @@ impl Position {
|
|||||||
let pos = match &self {
|
let pos = match &self {
|
||||||
Position::Stopped { node } => {
|
Position::Stopped { node } => {
|
||||||
// Entity is stationary at a node
|
// Entity is stationary at a node
|
||||||
let node = graph.get_node(*node).ok_or(EntityError::NodeNotFound(*node))?;
|
let node = graph.get_node(*node).ok_or(EntityError::NodeNotFound(*node as usize))?;
|
||||||
node.position
|
node.position
|
||||||
}
|
}
|
||||||
Position::Moving {
|
Position::Moving {
|
||||||
@@ -66,11 +66,12 @@ impl Position {
|
|||||||
remaining_distance,
|
remaining_distance,
|
||||||
} => {
|
} => {
|
||||||
// Entity is traveling between nodes
|
// Entity is traveling between nodes
|
||||||
let from_node = graph.get_node(*from).ok_or(EntityError::NodeNotFound(*from))?;
|
let from_node = graph.get_node(*from).ok_or(EntityError::NodeNotFound(*from as usize))?;
|
||||||
let to_node = graph.get_node(*to).ok_or(EntityError::NodeNotFound(*to))?;
|
let to_node = graph.get_node(*to).ok_or(EntityError::NodeNotFound(*to as usize))?;
|
||||||
let edge = graph
|
let edge = graph.find_edge(*from, *to).ok_or(EntityError::EdgeNotFound {
|
||||||
.find_edge(*from, *to)
|
from: *from as usize,
|
||||||
.ok_or(EntityError::EdgeNotFound { from: *from, to: *to })?;
|
to: *to as usize,
|
||||||
|
})?;
|
||||||
|
|
||||||
// For zero-distance edges (tunnels), progress >= 1.0 means we're at the target
|
// For zero-distance edges (tunnels), progress >= 1.0 means we're at the target
|
||||||
if edge.distance == 0.0 {
|
if edge.distance == 0.0 {
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ use pacman::{
|
|||||||
events::GameEvent,
|
events::GameEvent,
|
||||||
map::builder::Map,
|
map::builder::Map,
|
||||||
systems::{
|
systems::{
|
||||||
check_collision, collision_system, Collider, EntityType, Ghost, GhostCollider, ItemCollider, PacmanCollider, Position,
|
check_collision, collision_system, Collider, EntityType, Ghost, GhostCollider, ItemCollider, NodeId, PacmanCollider,
|
||||||
|
Position,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -59,7 +60,7 @@ fn spawn_test_ghost(world: &mut World) -> Entity {
|
|||||||
fn spawn_test_ghost_at_node(world: &mut World, node: usize) -> Entity {
|
fn spawn_test_ghost_at_node(world: &mut World, node: usize) -> Entity {
|
||||||
world
|
world
|
||||||
.spawn((
|
.spawn((
|
||||||
Position::Stopped { node },
|
Position::Stopped { node: node as NodeId },
|
||||||
Collider { size: 12.0 },
|
Collider { size: 12.0 },
|
||||||
GhostCollider,
|
GhostCollider,
|
||||||
Ghost::Blinky,
|
Ghost::Blinky,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use glam::IVec2;
|
use glam::I8Vec2;
|
||||||
use pacman::map::direction::*;
|
use pacman::map::direction::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -18,14 +18,14 @@ fn test_direction_opposite() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_direction_as_ivec2() {
|
fn test_direction_as_ivec2() {
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
(Direction::Up, -IVec2::Y),
|
(Direction::Up, -I8Vec2::Y),
|
||||||
(Direction::Down, IVec2::Y),
|
(Direction::Down, I8Vec2::Y),
|
||||||
(Direction::Left, -IVec2::X),
|
(Direction::Left, -I8Vec2::X),
|
||||||
(Direction::Right, IVec2::X),
|
(Direction::Right, I8Vec2::X),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (dir, expected) in test_cases {
|
for (dir, expected) in test_cases {
|
||||||
assert_eq!(dir.as_ivec2(), expected);
|
assert_eq!(dir.as_ivec2(), expected);
|
||||||
assert_eq!(IVec2::from(dir), expected);
|
assert_eq!(I8Vec2::from(dir), expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,10 @@ fn test_map_node_positions() {
|
|||||||
|
|
||||||
for (grid_pos, &node_id) in &map.grid_to_node {
|
for (grid_pos, &node_id) in &map.grid_to_node {
|
||||||
let node = map.graph.get_node(node_id).unwrap();
|
let node = map.graph.get_node(node_id).unwrap();
|
||||||
let expected_pos = Vec2::new((grid_pos.x * CELL_SIZE as i32) as f32, (grid_pos.y * CELL_SIZE as i32) as f32)
|
let expected_pos = Vec2::new(
|
||||||
+ Vec2::splat(CELL_SIZE as f32 / 2.0);
|
(grid_pos.x as i32 * CELL_SIZE as i32) as f32,
|
||||||
|
(grid_pos.y as i32 * CELL_SIZE as i32) as f32,
|
||||||
|
) + Vec2::splat(CELL_SIZE as f32 / 2.0);
|
||||||
|
|
||||||
assert_eq!(node.position, expected_pos);
|
assert_eq!(node.position, expected_pos);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user