diff --git a/src/game.rs b/src/game.rs index 703e09b..7c57996 100644 --- a/src/game.rs +++ b/src/game.rs @@ -11,7 +11,7 @@ use crate::systems::blinking::Blinking; use crate::systems::movement::{BufferedDirection, Position, Velocity}; use crate::systems::profiling::SystemId; 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::{ 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, @@ -303,7 +303,7 @@ impl Game { .ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound("maze/energizer.png".to_string())))?; // 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::() .iter_nodes() .filter_map(|(id, tile)| match tile { diff --git a/src/map/builder.rs b/src/map/builder.rs index 0cd9349..64028b8 100644 --- a/src/map/builder.rs +++ b/src/map/builder.rs @@ -5,7 +5,7 @@ use crate::map::graph::{Graph, Node, TraversalFlags}; use crate::map::parser::MapTileParser; use crate::systems::movement::NodeId; use bevy_ecs::resource::Resource; -use glam::{IVec2, Vec2}; +use glam::{I8Vec2, IVec2, Vec2}; use std::collections::{HashMap, VecDeque}; use tracing::debug; @@ -38,7 +38,7 @@ pub struct Map { /// Connected graph of navigable positions. pub graph: Graph, /// Bidirectional mapping between 2D grid coordinates and graph node indices. - pub grid_to_node: HashMap, + pub grid_to_node: HashMap, /// Predetermined spawn locations for all game entities pub start_positions: NodePositions, /// 2D array of tile types for collision detection and rendering @@ -76,8 +76,8 @@ impl Map { let mut queue = VecDeque::new(); queue.push_back(start_pos); let pos = Vec2::new( - (start_pos.x * CELL_SIZE as i32) as f32, - (start_pos.y * CELL_SIZE as i32) as f32, + (start_pos.x as i32 * CELL_SIZE as i32) as f32, + (start_pos.y as i32 * CELL_SIZE as i32) as f32, ) + cell_offset; let node_id = graph.add_node(Node { position: pos }); grid_to_node.insert(start_pos, node_id); @@ -89,9 +89,9 @@ impl Map { // Skip if the new position is out of bounds 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 >= BOARD_CELL_SIZE.y as i32 + || new_position.y as i32 >= BOARD_CELL_SIZE.y as i32 { continue; } @@ -108,8 +108,8 @@ impl Map { ) { // Add the new position to the graph/queue let pos = Vec2::new( - (new_position.x * CELL_SIZE as i32) as f32, - (new_position.y * CELL_SIZE as i32) as f32, + (new_position.x as i32 * CELL_SIZE as i32) as f32, + (new_position.y as i32 * CELL_SIZE as i32) as f32, ) + cell_offset; let new_node_id = graph.add_node(Node { position: pos }); 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 dir in Direction::DIRECTIONS { // 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(); // If the neighbor exists, connect the node to it 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. fn build_house( graph: &mut Graph, - grid_to_node: &HashMap, - house_door: &[Option; 2], - ) -> GameResult<(usize, usize, usize, usize)> { + grid_to_node: &HashMap, + house_door: &[Option; 2], + ) -> GameResult<(NodeId, NodeId, NodeId, NodeId)> { // Calculate the position of the house entrance node let (house_entrance_node_id, house_entrance_node_position) = { // Translate the grid positions to the actual node ids @@ -222,10 +222,13 @@ impl Map { // Calculate the position of the house node 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 .get_node(*right_node) - .ok_or(MapError::NodeNotFound(*right_node))? + .ok_or(MapError::NodeNotFound(*right_node as usize))? .position; let house_node = graph.add_node(Node { position: left_pos.lerp(right_pos, 0.5), @@ -249,10 +252,10 @@ impl Map { // Place the nodes at, above, and below the center position let center_node_id = graph.add_node(Node { position: center_pos }); 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 { - 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 @@ -268,7 +271,7 @@ impl Map { // Calculate the position of the center line's center node 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 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 let (left_center_node_id, _) = create_house_line( 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 let (right_center_node_id, _) = create_house_line( 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}"); @@ -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. fn build_tunnels( graph: &mut Graph, - grid_to_node: &HashMap, - tunnel_ends: &[Option; 2], + grid_to_node: &HashMap, + tunnel_ends: &[Option; 2], ) -> GameResult<()> { // Create the hidden tunnel nodes let left_tunnel_hidden_node_id = { @@ -353,7 +356,7 @@ impl Map { Direction::Left, Node { 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| { @@ -378,7 +381,7 @@ impl Map { Direction::Right, Node { 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| { diff --git a/src/map/direction.rs b/src/map/direction.rs index 137cc26..3cdeee0 100644 --- a/src/map/direction.rs +++ b/src/map/direction.rs @@ -1,4 +1,4 @@ -use glam::IVec2; +use glam::I8Vec2; use strum_macros::AsRefStr; /// The four cardinal directions. @@ -28,8 +28,8 @@ impl Direction { } } - /// Returns the direction as an IVec2. - pub fn as_ivec2(self) -> IVec2 { + /// Returns the direction as an I8Vec2. + pub fn as_ivec2(self) -> I8Vec2 { self.into() } @@ -45,13 +45,13 @@ impl Direction { } } -impl From for IVec2 { +impl From for I8Vec2 { fn from(dir: Direction) -> Self { match dir { - Direction::Up => -IVec2::Y, - Direction::Down => IVec2::Y, - Direction::Left => -IVec2::X, - Direction::Right => IVec2::X, + Direction::Up => -I8Vec2::Y, + Direction::Down => I8Vec2::Y, + Direction::Left => -I8Vec2::X, + Direction::Right => I8Vec2::X, } } } diff --git a/src/map/graph.rs b/src/map/graph.rs index 27e138b..286eadf 100644 --- a/src/map/graph.rs +++ b/src/map/graph.rs @@ -107,7 +107,7 @@ impl Graph { /// 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(); + let id = self.nodes.len() as NodeId; self.nodes.push(data); self.adjacency_list.push(Intersection::default()); id @@ -129,10 +129,10 @@ impl Graph { distance: Option, direction: Direction, ) -> Result<(), &'static str> { - if from >= self.adjacency_list.len() { + if from as usize >= self.adjacency_list.len() { 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."); } @@ -178,8 +178,8 @@ impl Graph { } 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; + let from_pos = self.nodes[from as usize].position; + let to_pos = self.nodes[to as usize].position; from_pos.distance(to_pos) } }, @@ -187,11 +187,11 @@ impl Graph { traversal_flags, }; - if from >= self.adjacency_list.len() { + if from as usize >= self.adjacency_list.len() { 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 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. 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. @@ -228,17 +228,17 @@ impl Graph { self.adjacency_list .iter() .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. pub fn find_edge(&self, from: NodeId, to: NodeId) -> Option { - 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. pub fn find_edge_in_direction(&self, from: NodeId, direction: Direction) -> Option { - self.adjacency_list.get(from)?.get(direction) + self.adjacency_list.get(from as usize)?.get(direction) } } diff --git a/src/map/parser.rs b/src/map/parser.rs index 0633167..0ed9ada 100644 --- a/src/map/parser.rs +++ b/src/map/parser.rs @@ -2,7 +2,7 @@ use crate::constants::{MapTile, BOARD_CELL_SIZE}; use crate::error::ParseError; -use glam::IVec2; +use glam::I8Vec2; /// 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 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) - pub house_door: [Option; 2], + pub house_door: [Option; 2], /// Two positions marking tunnel portals for wraparound teleportation ('T' characters) - pub tunnel_ends: [Option; 2], + pub tunnel_ends: [Option; 2], /// Starting position for Pac-Man (marked by 'X' character in the layout) - pub pacman_start: Option, + pub pacman_start: Option, } /// 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 house_door = [None; 2]; let mut tunnel_ends = [None; 2]; - let mut pacman_start: Option = None; + let mut pacman_start: Option = None; 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) { @@ -98,16 +98,16 @@ impl MapTileParser { match tile { MapTile::Tunnel => { 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 { - 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 == '=' => { 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 { - 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 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; diff --git a/src/systems/debug.rs b/src/systems/debug.rs index 606a332..e65d8c5 100644 --- a/src/systems/debug.rs +++ b/src/systems/debug.rs @@ -3,7 +3,7 @@ use std::cmp::Ordering; use crate::constants::BOARD_PIXEL_OFFSET; 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::system::{NonSendMut, Query, Res}; use glam::{IVec2, UVec2, Vec2}; @@ -185,7 +185,7 @@ pub fn debug_render_system( // Render node ID if a node is highlighted 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 surface = font diff --git a/src/systems/ghost.rs b/src/systems/ghost.rs index 863352d..caece48 100644 --- a/src/systems/ghost.rs +++ b/src/systems/ghost.rs @@ -36,7 +36,7 @@ pub fn ghost_movement_system( loop { match *position { 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 mut non_opposite_options: SmallVec<[Edge; 3]> = SmallVec::new(); @@ -159,8 +159,11 @@ pub fn eaten_ghost_system( velocity.direction = direction; *position = Position::Moving { from: current_node, - to: map.graph.adjacency_list[current_node].get(direction).unwrap().target, - remaining_distance: map.graph.adjacency_list[current_node].get(direction).unwrap().distance, + to: map.graph.adjacency_list[current_node as usize].get(direction).unwrap().target, + 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; *position = Position::Moving { from: to, - to: map.graph.adjacency_list[to].get(next_direction).unwrap().target, - remaining_distance: map.graph.adjacency_list[to].get(next_direction).unwrap().distance, + to: map.graph.adjacency_list[to as usize].get(next_direction).unwrap().target, + 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. /// Uses simple greedy pathfinding - prefers straight lines when possible. -fn find_direction_to_target(map: &Map, from_node: usize, target_node: usize) -> Option { +fn find_direction_to_target( + map: &Map, + from_node: crate::systems::movement::NodeId, + target_node: crate::systems::movement::NodeId, +) -> Option { let from_pos = map.graph.get_node(from_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 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) { return Some(direction); } diff --git a/src/systems/movement.rs b/src/systems/movement.rs index f05978c..703e835 100644 --- a/src/systems/movement.rs +++ b/src/systems/movement.rs @@ -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 /// 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. /// Speed is static, only applied when the entity has an edge to traverse. @@ -57,7 +57,7 @@ impl Position { let pos = match &self { Position::Stopped { 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 } Position::Moving { @@ -66,11 +66,12 @@ impl Position { remaining_distance, } => { // Entity is traveling between nodes - let from_node = graph.get_node(*from).ok_or(EntityError::NodeNotFound(*from))?; - let to_node = graph.get_node(*to).ok_or(EntityError::NodeNotFound(*to))?; - let edge = graph - .find_edge(*from, *to) - .ok_or(EntityError::EdgeNotFound { from: *from, to: *to })?; + 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 as usize))?; + let edge = graph.find_edge(*from, *to).ok_or(EntityError::EdgeNotFound { + from: *from as usize, + to: *to as usize, + })?; // For zero-distance edges (tunnels), progress >= 1.0 means we're at the target if edge.distance == 0.0 { diff --git a/tests/collision.rs b/tests/collision.rs index 64c912b..3531f17 100644 --- a/tests/collision.rs +++ b/tests/collision.rs @@ -5,7 +5,8 @@ use pacman::{ events::GameEvent, map::builder::Map, 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 { world .spawn(( - Position::Stopped { node }, + Position::Stopped { node: node as NodeId }, Collider { size: 12.0 }, GhostCollider, Ghost::Blinky, diff --git a/tests/direction.rs b/tests/direction.rs index 6e9f1a0..895483e 100644 --- a/tests/direction.rs +++ b/tests/direction.rs @@ -1,4 +1,4 @@ -use glam::IVec2; +use glam::I8Vec2; use pacman::map::direction::*; #[test] @@ -18,14 +18,14 @@ fn test_direction_opposite() { #[test] fn test_direction_as_ivec2() { let test_cases = [ - (Direction::Up, -IVec2::Y), - (Direction::Down, IVec2::Y), - (Direction::Left, -IVec2::X), - (Direction::Right, IVec2::X), + (Direction::Up, -I8Vec2::Y), + (Direction::Down, I8Vec2::Y), + (Direction::Left, -I8Vec2::X), + (Direction::Right, I8Vec2::X), ]; for (dir, expected) in test_cases { assert_eq!(dir.as_ivec2(), expected); - assert_eq!(IVec2::from(dir), expected); + assert_eq!(I8Vec2::from(dir), expected); } } diff --git a/tests/map_builder.rs b/tests/map_builder.rs index 33423d0..dcacbf5 100644 --- a/tests/map_builder.rs +++ b/tests/map_builder.rs @@ -26,8 +26,10 @@ fn test_map_node_positions() { for (grid_pos, &node_id) in &map.grid_to_node { 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) - + Vec2::splat(CELL_SIZE as f32 / 2.0); + let expected_pos = Vec2::new( + (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); }