From d47d70ff5bbac58ae01620a81d20846c035a37d1 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 16 Aug 2025 14:37:14 -0500 Subject: [PATCH] refactor: remove dead code, move direction & graph into 'map' module --- src/entity/collision.rs | 128 ---------------- src/entity/ghost.rs | 254 ------------------------------- src/entity/item.rs | 117 -------------- src/entity/mod.rs | 7 - src/entity/pacman.rs | 115 -------------- src/entity/trait.rs | 114 -------------- src/error.rs | 15 +- src/events.rs | 6 +- src/{game/mod.rs => game.rs} | 4 +- src/game/state.rs | 153 ------------------- src/helpers.rs | 10 -- src/lib.rs | 2 - src/main.rs | 2 - src/map/builder.rs | 4 +- src/{entity => map}/direction.rs | 0 src/{entity => map}/graph.rs | 0 src/map/mod.rs | 2 + src/systems/components.rs | 3 +- src/systems/ghost.rs | 11 +- src/systems/input.rs | 2 +- src/systems/movement.rs | 4 +- src/systems/player.rs | 2 +- src/systems/render.rs | 8 +- tests/direction.rs | 2 +- tests/graph.rs | 4 +- tests/helpers.rs | 19 --- tests/item.rs | 46 ------ 27 files changed, 31 insertions(+), 1003 deletions(-) delete mode 100644 src/entity/collision.rs delete mode 100644 src/entity/ghost.rs delete mode 100644 src/entity/item.rs delete mode 100644 src/entity/mod.rs delete mode 100644 src/entity/pacman.rs delete mode 100644 src/entity/trait.rs rename src/{game/mod.rs => game.rs} (99%) delete mode 100644 src/game/state.rs delete mode 100644 src/helpers.rs rename src/{entity => map}/direction.rs (100%) rename src/{entity => map}/graph.rs (100%) delete mode 100644 tests/helpers.rs delete mode 100644 tests/item.rs diff --git a/src/entity/collision.rs b/src/entity/collision.rs deleted file mode 100644 index 0401751..0000000 --- a/src/entity/collision.rs +++ /dev/null @@ -1,128 +0,0 @@ -// use smallvec::SmallVec; -// use std::collections::HashMap; - -// use crate::entity::{graph::NodeId, traversal::Position}; - -// /// Trait for entities that can participate in collision detection. -// pub trait Collidable { -// /// Returns the current position of this entity. -// fn position(&self) -> Position; - -// /// Checks if this entity is colliding with another entity. -// #[allow(dead_code)] -// fn is_colliding_with(&self, other: &dyn Collidable) -> bool { -// positions_overlap(&self.position(), &other.position()) -// } -// } - -// /// System for tracking entities by their positions for efficient collision detection. -// #[derive(Default)] -// pub struct CollisionSystem { -// /// Maps node IDs to lists of entity IDs that are at that node -// node_entities: HashMap>, -// /// Maps entity IDs to their current positions -// entity_positions: HashMap, -// /// Next available entity ID -// next_id: EntityId, -// } - -// /// Unique identifier for an entity in the collision system -// pub type EntityId = u32; - -// impl CollisionSystem { -// /// Registers an entity with the collision system and returns its ID -// pub fn register_entity(&mut self, position: Position) -> EntityId { -// let id = self.next_id; -// self.next_id += 1; - -// self.entity_positions.insert(id, position); -// self.update_node_entities(id, position); - -// id -// } - -// /// Updates an entity's position -// pub fn update_position(&mut self, entity_id: EntityId, new_position: Position) { -// if let Some(old_position) = self.entity_positions.get(&entity_id) { -// // Remove from old nodes -// self.remove_from_nodes(entity_id, *old_position); -// } - -// // Update position and add to new nodes -// self.entity_positions.insert(entity_id, new_position); -// self.update_node_entities(entity_id, new_position); -// } - -// /// Removes an entity from the collision system -// #[allow(dead_code)] -// pub fn remove_entity(&mut self, entity_id: EntityId) { -// if let Some(position) = self.entity_positions.remove(&entity_id) { -// self.remove_from_nodes(entity_id, position); -// } -// } - -// /// Gets all entity IDs at a specific node -// pub fn entities_at_node(&self, node: NodeId) -> &[EntityId] { -// self.node_entities.get(&node).map(|v| v.as_slice()).unwrap_or(&[]) -// } - -// /// Gets all entity IDs that could collide with an entity at the given position -// pub fn potential_collisions(&self, position: &Position) -> Vec { -// let mut collisions = Vec::new(); -// let nodes = get_nodes(position); - -// for node in nodes { -// collisions.extend(self.entities_at_node(node)); -// } - -// // Remove duplicates -// collisions.sort_unstable(); -// collisions.dedup(); -// collisions -// } - -// /// Updates the node_entities map when an entity's position changes -// fn update_node_entities(&mut self, entity_id: EntityId, position: Position) { -// let nodes = get_nodes(&position); -// for node in nodes { -// self.node_entities.entry(node).or_default().push(entity_id); -// } -// } - -// /// Removes an entity from all nodes it was previously at -// fn remove_from_nodes(&mut self, entity_id: EntityId, position: Position) { -// let nodes = get_nodes(&position); -// for node in nodes { -// if let Some(entities) = self.node_entities.get_mut(&node) { -// entities.retain(|&id| id != entity_id); -// if entities.is_empty() { -// self.node_entities.remove(&node); -// } -// } -// } -// } -// } - -// /// Checks if two positions overlap (entities are at the same location). -// fn positions_overlap(a: &Position, b: &Position) -> bool { -// let a_nodes = get_nodes(a); -// let b_nodes = get_nodes(b); - -// // Check if any nodes overlap -// a_nodes.iter().any(|a_node| b_nodes.contains(a_node)) - -// // TODO: More complex overlap detection, the above is a simple check, but it could become an early filter for more precise calculations later -// } - -// /// Gets all nodes that an entity is currently at or between. -// fn get_nodes(pos: &Position) -> SmallVec<[NodeId; 2]> { -// let mut nodes = SmallVec::new(); -// match pos { -// Position::AtNode(node) => nodes.push(*node), -// Position::BetweenNodes { from, to, .. } => { -// nodes.push(*from); -// nodes.push(*to); -// } -// } -// nodes -// } diff --git a/src/entity/ghost.rs b/src/entity/ghost.rs deleted file mode 100644 index 7638d11..0000000 --- a/src/entity/ghost.rs +++ /dev/null @@ -1,254 +0,0 @@ -// //! Ghost entity implementation. -// //! -// //! This module contains the ghost character logic, including movement, -// //! animation, and rendering. Ghosts move through the game graph using -// //! a traverser and display directional animated textures. - -// use pathfinding::prelude::dijkstra; -// use rand::prelude::*; -// use smallvec::SmallVec; -// use tracing::error; - -// use crate::entity::{ -// collision::Collidable, -// direction::Direction, -// graph::{Edge, EdgePermissions, Graph, NodeId}, -// r#trait::Entity, -// traversal::Traverser, -// }; -// use crate::texture::animated::AnimatedTexture; -// use crate::texture::directional::DirectionalAnimatedTexture; -// use crate::texture::sprite::SpriteAtlas; - -// use crate::error::{EntityError, GameError, GameResult, TextureError}; - -// /// Determines if a ghost can traverse a given edge. -// /// -// /// Ghosts can move through edges that allow all entities or ghost-only edges. -// fn can_ghost_traverse(edge: Edge) -> bool { -// matches!(edge.permissions, EdgePermissions::All | EdgePermissions::GhostsOnly) -// } - -// /// The four classic ghost types. -// #[derive(Debug, Clone, Copy, PartialEq, Eq)] -// pub enum GhostType { -// Blinky, -// Pinky, -// Inky, -// Clyde, -// } - -// impl GhostType { -// /// Returns the ghost type name for atlas lookups. -// pub fn as_str(self) -> &'static str { -// match self { -// GhostType::Blinky => "blinky", -// GhostType::Pinky => "pinky", -// GhostType::Inky => "inky", -// GhostType::Clyde => "clyde", -// } -// } - -// /// Returns the base movement speed for this ghost type. -// pub fn base_speed(self) -> f32 { -// match self { -// GhostType::Blinky => 1.0, -// GhostType::Pinky => 0.95, -// GhostType::Inky => 0.9, -// GhostType::Clyde => 0.85, -// } -// } -// } - -// /// A ghost entity that roams the game world. -// /// -// /// Ghosts move through the game world using a graph-based navigation system -// /// and display directional animated sprites. They randomly choose directions -// /// at each intersection. -// pub struct Ghost { -// /// Handles movement through the game graph -// pub traverser: Traverser, -// /// The type of ghost (affects appearance and speed) -// pub ghost_type: GhostType, -// /// Manages directional animated textures for different movement states -// texture: DirectionalAnimatedTexture, -// /// Current movement speed -// speed: f32, -// } - -// impl Entity for Ghost { -// fn traverser(&self) -> &Traverser { -// &self.traverser -// } - -// fn traverser_mut(&mut self) -> &mut Traverser { -// &mut self.traverser -// } - -// fn texture(&self) -> &DirectionalAnimatedTexture { -// &self.texture -// } - -// fn texture_mut(&mut self) -> &mut DirectionalAnimatedTexture { -// &mut self.texture -// } - -// fn speed(&self) -> f32 { -// self.speed -// } - -// fn can_traverse(&self, edge: Edge) -> bool { -// can_ghost_traverse(edge) -// } - -// fn tick(&mut self, dt: f32, graph: &Graph) { -// // Choose random direction when at a node -// if self.traverser.position.is_at_node() { -// self.choose_random_direction(graph); -// } - -// if let Err(e) = self.traverser.advance(graph, dt * 60.0 * self.speed, &can_ghost_traverse) { -// error!("Ghost movement error: {}", e); -// } -// self.texture.tick(dt); -// } -// } - -// impl Ghost { -// /// Creates a new ghost instance at the specified starting node. -// /// -// /// Sets up animated textures for all four directions with moving and stopped states. -// /// The moving animation cycles through two sprite variants. -// pub fn new(graph: &Graph, start_node: NodeId, ghost_type: GhostType, atlas: &SpriteAtlas) -> GameResult { -// let mut textures = [None, None, None, None]; -// let mut stopped_textures = [None, None, None, None]; - -// for direction in Direction::DIRECTIONS { -// let moving_prefix = match direction { -// Direction::Up => "up", -// Direction::Down => "down", -// Direction::Left => "left", -// Direction::Right => "right", -// }; -// let moving_tiles = vec![ -// SpriteAtlas::get_tile(atlas, &format!("ghost/{}/{}_{}.png", ghost_type.as_str(), moving_prefix, "a")) -// .ok_or_else(|| { -// GameError::Texture(TextureError::AtlasTileNotFound(format!( -// "ghost/{}/{}_{}.png", -// ghost_type.as_str(), -// moving_prefix, -// "a" -// ))) -// })?, -// SpriteAtlas::get_tile(atlas, &format!("ghost/{}/{}_{}.png", ghost_type.as_str(), moving_prefix, "b")) -// .ok_or_else(|| { -// GameError::Texture(TextureError::AtlasTileNotFound(format!( -// "ghost/{}/{}_{}.png", -// ghost_type.as_str(), -// moving_prefix, -// "b" -// ))) -// })?, -// ]; - -// let stopped_tiles = -// vec![ -// SpriteAtlas::get_tile(atlas, &format!("ghost/{}/{}_{}.png", ghost_type.as_str(), moving_prefix, "a")) -// .ok_or_else(|| { -// GameError::Texture(TextureError::AtlasTileNotFound(format!( -// "ghost/{}/{}_{}.png", -// ghost_type.as_str(), -// moving_prefix, -// "a" -// ))) -// })?, -// ]; - -// textures[direction.as_usize()] = Some(AnimatedTexture::new(moving_tiles, 0.2)?); -// stopped_textures[direction.as_usize()] = Some(AnimatedTexture::new(stopped_tiles, 0.1)?); -// } - -// Ok(Self { -// traverser: Traverser::new(graph, start_node, Direction::Left, &can_ghost_traverse), -// ghost_type, -// texture: DirectionalAnimatedTexture::new(textures, stopped_textures), -// speed: ghost_type.base_speed(), -// }) -// } - -// /// Chooses a random available direction at the current intersection. -// fn choose_random_direction(&mut self, graph: &Graph) { -// let current_node = self.traverser.position.from_node_id(); -// let intersection = &graph.adjacency_list[current_node]; - -// // Collect all available directions -// let mut available_directions = SmallVec::<[_; 4]>::new(); -// for direction in Direction::DIRECTIONS { -// if let Some(edge) = intersection.get(direction) { -// if can_ghost_traverse(edge) { -// available_directions.push(direction); -// } -// } -// } -// // Choose a random direction (avoid reversing unless necessary) -// if !available_directions.is_empty() { -// let mut rng = SmallRng::from_os_rng(); - -// // Filter out the opposite direction if possible, but allow it if we have limited options -// let opposite = self.traverser.direction.opposite(); -// let filtered_directions: Vec<_> = available_directions -// .iter() -// .filter(|&&dir| dir != opposite || available_directions.len() <= 2) -// .collect(); - -// if let Some(&random_direction) = filtered_directions.choose(&mut rng) { -// self.traverser.set_next_direction(*random_direction); -// } -// } -// } - -// /// Calculates the shortest path from the ghost's current position to a target node using Dijkstra's algorithm. -// /// -// /// Returns a vector of NodeIds representing the path, or an error if pathfinding fails. -// /// The path includes the current node and the target node. -// pub fn calculate_path_to_target(&self, graph: &Graph, target: NodeId) -> GameResult> { -// let start_node = self.traverser.position.from_node_id(); - -// // Use Dijkstra's algorithm to find the shortest path -// let result = dijkstra( -// &start_node, -// |&node_id| { -// // Get all edges from the current node -// graph.adjacency_list[node_id] -// .edges() -// .filter(|edge| can_ghost_traverse(*edge)) -// .map(|edge| (edge.target, (edge.distance * 100.0) as u32)) -// .collect::>() -// }, -// |&node_id| node_id == target, -// ); - -// result.map(|(path, _cost)| path).ok_or_else(|| { -// GameError::Entity(EntityError::PathfindingFailed(format!( -// "No path found from node {} to target {}", -// start_node, target -// ))) -// }) -// } - -// /// Returns the ghost's color for debug rendering. -// pub fn debug_color(&self) -> sdl2::pixels::Color { -// match self.ghost_type { -// GhostType::Blinky => sdl2::pixels::Color::RGB(255, 0, 0), // Red -// GhostType::Pinky => sdl2::pixels::Color::RGB(255, 182, 255), // Pink -// GhostType::Inky => sdl2::pixels::Color::RGB(0, 255, 255), // Cyan -// GhostType::Clyde => sdl2::pixels::Color::RGB(255, 182, 85), // Orange -// } -// } -// } - -// impl Collidable for Ghost { -// fn position(&self) -> crate::entity::traversal::Position { -// self.traverser.position -// } -// } diff --git a/src/entity/item.rs b/src/entity/item.rs deleted file mode 100644 index 1d662a9..0000000 --- a/src/entity/item.rs +++ /dev/null @@ -1,117 +0,0 @@ -// use crate::{ -// constants, -// entity::{collision::Collidable, graph::Graph}, -// error::{EntityError, GameResult}, -// texture::sprite::{Sprite, SpriteAtlas}, -// }; -// use sdl2::render::{Canvas, RenderTarget}; -// use strum_macros::{EnumCount, EnumIter}; - -// #[derive(Debug, Clone, Copy, PartialEq, Eq)] -// pub enum ItemType { -// Pellet, -// Energizer, -// #[allow(dead_code)] -// Fruit { -// kind: FruitKind, -// }, -// } - -// impl ItemType { -// pub fn get_score(self) -> u32 { -// match self { -// ItemType::Pellet => 10, -// ItemType::Energizer => 50, -// ItemType::Fruit { kind } => kind.get_score(), -// } -// } -// } - -// #[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCount)] -// #[allow(dead_code)] -// pub enum FruitKind { -// Apple, -// Strawberry, -// Orange, -// Melon, -// Bell, -// Key, -// Galaxian, -// } - -// impl FruitKind { -// #[allow(dead_code)] -// pub fn index(self) -> u8 { -// match self { -// FruitKind::Apple => 0, -// FruitKind::Strawberry => 1, -// FruitKind::Orange => 2, -// FruitKind::Melon => 3, -// FruitKind::Bell => 4, -// FruitKind::Key => 5, -// FruitKind::Galaxian => 6, -// } -// } - -// pub fn get_score(self) -> u32 { -// match self { -// FruitKind::Apple => 100, -// FruitKind::Strawberry => 300, -// FruitKind::Orange => 500, -// FruitKind::Melon => 700, -// FruitKind::Bell => 1000, -// FruitKind::Key => 2000, -// FruitKind::Galaxian => 3000, -// } -// } -// } - -// pub struct Item { -// pub node_index: usize, -// pub item_type: ItemType, -// pub sprite: Sprite, -// pub collected: bool, -// } - -// impl Item { -// pub fn new(node_index: usize, item_type: ItemType, sprite: Sprite) -> Self { -// Self { -// node_index, -// item_type, -// sprite, -// collected: false, -// } -// } - -// pub fn is_collected(&self) -> bool { -// self.collected -// } - -// pub fn collect(&mut self) { -// self.collected = true; -// } - -// pub fn get_score(&self) -> u32 { -// self.item_type.get_score() -// } - -// pub fn render(&self, canvas: &mut Canvas, atlas: &mut SpriteAtlas, graph: &Graph) -> GameResult<()> { -// if self.collected { -// return Ok(()); -// } - -// let node = graph -// .get_node(self.node_index) -// .ok_or(EntityError::NodeNotFound(self.node_index))?; -// let position = node.position + constants::BOARD_PIXEL_OFFSET.as_vec2(); - -// self.sprite.render(canvas, atlas, position)?; -// Ok(()) -// } -// } - -// impl Collidable for Item { -// fn position(&self) -> crate::entity::traversal::Position { -// crate::entity::traversal::Position::AtNode(self.node_index) -// } -// } diff --git a/src/entity/mod.rs b/src/entity/mod.rs deleted file mode 100644 index d05c62d..0000000 --- a/src/entity/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod collision; -pub mod direction; -pub mod ghost; -pub mod graph; -pub mod item; -pub mod pacman; -pub mod r#trait; diff --git a/src/entity/pacman.rs b/src/entity/pacman.rs deleted file mode 100644 index d881968..0000000 --- a/src/entity/pacman.rs +++ /dev/null @@ -1,115 +0,0 @@ -// //! Pac-Man entity implementation. -// //! -// //! This module contains the main player character logic, including movement, -// //! animation, and rendering. Pac-Man moves through the game graph using -// //! a traverser and displays directional animated textures. - -// use crate::entity::{ -// collision::Collidable, -// direction::Direction, -// graph::{Edge, EdgePermissions, Graph, NodeId}, -// r#trait::Entity, -// traversal::Traverser, -// }; -// use crate::texture::animated::AnimatedTexture; -// use crate::texture::directional::DirectionalAnimatedTexture; -// use crate::texture::sprite::SpriteAtlas; -// use tracing::error; - -// use crate::error::{GameError, GameResult, TextureError}; - -// /// Determines if Pac-Man can traverse a given edge. -// /// -// /// Pac-Man can only move through edges that allow all entities. -// fn can_pacman_traverse(edge: Edge) -> bool { -// matches!(edge.permissions, EdgePermissions::All) -// } - -// /// The main player character entity. -// /// -// /// Pac-Man moves through the game world using a graph-based navigation system -// /// and displays directional animated sprites based on movement state. -// pub struct Pacman { -// /// Handles movement through the game graph -// pub traverser: Traverser, -// /// Manages directional animated textures for different movement states -// texture: DirectionalAnimatedTexture, -// } - -// impl Entity for Pacman { -// fn traverser(&self) -> &Traverser { -// &self.traverser -// } - -// fn traverser_mut(&mut self) -> &mut Traverser { -// &mut self.traverser -// } - -// fn texture(&self) -> &DirectionalAnimatedTexture { -// &self.texture -// } - -// fn texture_mut(&mut self) -> &mut DirectionalAnimatedTexture { -// &mut self.texture -// } - -// fn speed(&self) -> f32 { -// 1.125 -// } - -// fn can_traverse(&self, edge: Edge) -> bool { -// can_pacman_traverse(edge) -// } - -// fn tick(&mut self, dt: f32, graph: &Graph) { -// if let Err(e) = self.traverser.advance(graph, dt * 60.0 * 1.125, &can_pacman_traverse) { -// error!("Pac-Man movement error: {}", e); -// } -// self.texture.tick(dt); -// } -// } - -// impl Pacman { -// /// Creates a new Pac-Man instance at the specified starting node. -// /// -// /// Sets up animated textures for all four directions with moving and stopped states. -// /// The moving animation cycles through open mouth, closed mouth, and full sprites. -// pub fn new(graph: &Graph, start_node: NodeId, atlas: &SpriteAtlas) -> GameResult { -// let mut textures = [None, None, None, None]; -// let mut stopped_textures = [None, None, None, None]; - -// for direction in Direction::DIRECTIONS { -// let moving_prefix = match direction { -// Direction::Up => "pacman/up", -// Direction::Down => "pacman/down", -// Direction::Left => "pacman/left", -// Direction::Right => "pacman/right", -// }; -// let moving_tiles = vec![ -// SpriteAtlas::get_tile(atlas, &format!("{moving_prefix}_a.png")) -// .ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound(format!("{moving_prefix}_a.png"))))?, -// SpriteAtlas::get_tile(atlas, &format!("{moving_prefix}_b.png")) -// .ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound(format!("{moving_prefix}_b.png"))))?, -// SpriteAtlas::get_tile(atlas, "pacman/full.png") -// .ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound("pacman/full.png".to_string())))?, -// ]; - -// let stopped_tiles = vec![SpriteAtlas::get_tile(atlas, &format!("{moving_prefix}_b.png")) -// .ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound(format!("{moving_prefix}_b.png"))))?]; - -// textures[direction.as_usize()] = Some(AnimatedTexture::new(moving_tiles, 0.08)?); -// stopped_textures[direction.as_usize()] = Some(AnimatedTexture::new(stopped_tiles, 0.1)?); -// } - -// Ok(Self { -// traverser: Traverser::new(graph, start_node, Direction::Left, &can_pacman_traverse), -// texture: DirectionalAnimatedTexture::new(textures, stopped_textures), -// }) -// } -// } - -// impl Collidable for Pacman { -// fn position(&self) -> crate::entity::traversal::Position { -// self.traverser.position -// } -// } diff --git a/src/entity/trait.rs b/src/entity/trait.rs deleted file mode 100644 index b0f07e8..0000000 --- a/src/entity/trait.rs +++ /dev/null @@ -1,114 +0,0 @@ -// //! Entity trait for common movement and rendering functionality. -// //! -// //! This module defines a trait that captures the shared behavior between -// //! different game entities like Ghosts and Pac-Man, including movement, -// //! rendering, and position calculations. - -// use glam::Vec2; -// use sdl2::render::{Canvas, RenderTarget}; - -// use crate::entity::direction::Direction; -// use crate::entity::graph::{Edge, Graph, NodeId}; -// use crate::entity::traversal::{Position, Traverser}; -// use crate::error::{EntityError, GameError, GameResult, TextureError}; -// use crate::texture::directional::DirectionalAnimatedTexture; -// use crate::texture::sprite::SpriteAtlas; - -// /// Trait defining common functionality for game entities that move through the graph. -// /// -// /// This trait provides a unified interface for entities that: -// /// - Move through the game graph using a traverser -// /// - Render using directional animated textures -// /// - Have position calculations and movement speed -// #[allow(dead_code)] -// pub trait Entity { -// /// Returns a reference to the entity's traverser for movement control. -// fn traverser(&self) -> &Traverser; - -// /// Returns a mutable reference to the entity's traverser for movement control. -// fn traverser_mut(&mut self) -> &mut Traverser; - -// /// Returns a reference to the entity's directional animated texture. -// fn texture(&self) -> &DirectionalAnimatedTexture; - -// /// Returns a mutable reference to the entity's directional animated texture. -// fn texture_mut(&mut self) -> &mut DirectionalAnimatedTexture; - -// /// Returns the movement speed multiplier for this entity. -// fn speed(&self) -> f32; - -// /// Determines if this entity can traverse a given edge. -// fn can_traverse(&self, edge: Edge) -> bool; - -// /// Updates the entity's position and animation state. -// /// -// /// This method advances movement through the graph and updates texture animation. -// fn tick(&mut self, dt: f32, graph: &Graph); - -// /// Calculates the current pixel position in the game world. -// /// -// /// Converts the graph position to screen coordinates, accounting for -// /// the board offset and centering the sprite. -// fn get_pixel_pos(&self, graph: &Graph) -> GameResult { -// let pos = match self.traverser().position { -// Position::AtNode(node_id) => { -// let node = graph.get_node(node_id).ok_or(EntityError::NodeNotFound(node_id))?; -// node.position -// } -// Position::BetweenNodes { from, to, traversed } => { -// 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, to })?; -// from_node.position + (to_node.position - from_node.position) * (traversed / edge.distance) -// } -// }; - -// Ok(Vec2::new( -// pos.x + crate::constants::BOARD_PIXEL_OFFSET.x as f32, -// pos.y + crate::constants::BOARD_PIXEL_OFFSET.y as f32, -// )) -// } - -// /// Returns the current node ID that the entity is at or moving towards. -// /// -// /// If the entity is at a node, returns that node ID. -// /// If the entity is between nodes, returns the node it's moving towards. -// fn current_node_id(&self) -> NodeId { -// match self.traverser().position { -// Position::AtNode(node_id) => node_id, -// Position::BetweenNodes { to, .. } => to, -// } -// } - -// /// Sets the next direction for the entity to take. -// /// -// /// The direction is buffered and will be applied at the next opportunity, -// /// typically when the entity reaches a new node. -// fn set_next_direction(&mut self, direction: Direction) { -// self.traverser_mut().set_next_direction(direction); -// } - -// /// Renders the entity at its current position. -// /// -// /// Draws the appropriate directional sprite based on the entity's -// /// current movement state and direction. -// fn render(&self, canvas: &mut Canvas, atlas: &mut SpriteAtlas, graph: &Graph) -> GameResult<()> { -// let pixel_pos = self.get_pixel_pos(graph)?; -// let dest = crate::helpers::centered_with_size( -// glam::IVec2::new(pixel_pos.x as i32, pixel_pos.y as i32), -// glam::UVec2::new(16, 16), -// ); - -// if self.traverser().position.is_stopped() { -// self.texture() -// .render_stopped(canvas, atlas, dest, self.traverser().direction) -// .map_err(|e| GameError::Texture(TextureError::RenderFailed(e.to_string())))?; -// } else { -// self.texture() -// .render(canvas, atlas, dest, self.traverser().direction) -// .map_err(|e| GameError::Texture(TextureError::RenderFailed(e.to_string())))?; -// } - -// Ok(()) -// } -// } diff --git a/src/error.rs b/src/error.rs index 3c577d4..c559160 100644 --- a/src/error.rs +++ b/src/error.rs @@ -31,9 +31,6 @@ pub enum GameError { #[error("Entity error: {0}")] Entity(#[from] EntityError), - #[error("Game state error: {0}")] - GameState(#[from] GameStateError), - #[error("SDL error: {0}")] Sdl(String), @@ -51,6 +48,8 @@ pub enum GameError { pub enum AssetError { #[error("IO error: {0}")] Io(#[from] io::Error), + + #[allow(dead_code)] #[error("Asset not found: {0}")] NotFound(String), } @@ -109,18 +108,8 @@ pub enum EntityError { #[error("Edge not found: from {from} to {to}")] EdgeNotFound { from: usize, to: usize }, - - #[error("Invalid movement: {0}")] - InvalidMovement(String), - - #[error("Pathfinding failed: {0}")] - PathfindingFailed(String), } -/// Errors related to game state operations. -#[derive(thiserror::Error, Debug)] -pub enum GameStateError {} - /// Errors related to map operations. #[derive(thiserror::Error, Debug)] pub enum MapError { diff --git a/src/events.rs b/src/events.rs index 1f10d62..cb821dd 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,9 +1,11 @@ -use bevy_ecs::prelude::*; +use bevy_ecs::{entity::Entity, event::Event}; + +use crate::map::direction::Direction; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum GameCommand { Exit, - MovePlayer(crate::entity::direction::Direction), + MovePlayer(Direction), ToggleDebug, MuteAudio, ResetLevel, diff --git a/src/game/mod.rs b/src/game.rs similarity index 99% rename from src/game/mod.rs rename to src/game.rs index a87bad5..fa150b5 100644 --- a/src/game/mod.rs +++ b/src/game.rs @@ -3,10 +3,10 @@ include!(concat!(env!("OUT_DIR"), "/atlas_data.rs")); use crate::constants::CANVAS_SIZE; -use crate::entity::direction::Direction; use crate::error::{GameError, GameResult, TextureError}; use crate::events::GameEvent; use crate::map::builder::Map; +use crate::map::direction::Direction; use crate::systems::blinking::Blinking; use crate::systems::movement::{BufferedDirection, Position, Velocity}; use crate::systems::player::player_movement_system; @@ -48,8 +48,6 @@ use crate::{ texture::sprite::{AtlasMapper, SpriteAtlas}, }; -pub mod state; - /// The `Game` struct is the main entry point for the game. /// /// It contains the game's state and logic, and is responsible for diff --git a/src/game/state.rs b/src/game/state.rs deleted file mode 100644 index 14013a5..0000000 --- a/src/game/state.rs +++ /dev/null @@ -1,153 +0,0 @@ -// use std::collections::VecDeque; - -// use sdl2::{ -// image::LoadTexture, -// render::{Texture, TextureCreator}, -// video::WindowContext, -// }; -// use smallvec::SmallVec; - -// use crate::{ -// asset::{get_asset_bytes, Asset}, -// audio::Audio, -// constants::RAW_BOARD, -// entity::{ -// collision::{Collidable, CollisionSystem, EntityId}, -// ghost::{Ghost, GhostType}, -// item::Item, -// pacman::Pacman, -// }, -// error::{GameError, GameResult, TextureError}, -// game::events::GameEvent, -// map::builder::Map, -// texture::{ -// sprite::{AtlasMapper, SpriteAtlas}, -// text::TextTexture, -// }, -// }; - -// include!(concat!(env!("OUT_DIR"), "/atlas_data.rs")); - -// /// The `GameState` struct holds all the essential data for the game. -// /// -// /// This includes the score, map, entities (Pac-Man, ghosts, items), -// /// collision system, and rendering resources. By centralizing the game's state, -// /// we can cleanly separate it from the game's logic, making it easier to manage -// /// and reason about. -// pub struct GameState { -// pub paused: bool, - -// pub score: u32, -// pub map: Map, -// pub pacman: Pacman, -// pub pacman_id: EntityId, -// pub ghosts: SmallVec<[Ghost; 4]>, -// pub ghost_ids: SmallVec<[EntityId; 4]>, -// pub items: Vec, -// pub item_ids: Vec, -// pub debug_mode: bool, -// pub event_queue: VecDeque, - -// // Collision system -// pub(crate) collision_system: CollisionSystem, - -// // Rendering resources -// pub(crate) atlas: SpriteAtlas, -// pub(crate) text_texture: TextTexture, - -// // Audio -// pub audio: Audio, - -// // Map texture pre-rendering -// pub(crate) map_texture: Option>, -// pub(crate) map_rendered: bool, -// pub(crate) texture_creator: &'static TextureCreator, -// } - -// impl GameState { -// /// Creates a new `GameState` by initializing all the game's data. -// /// -// /// This function sets up the map, Pac-Man, ghosts, items, collision system, -// /// and all rendering resources required to start the game. It returns a `GameResult` -// /// to handle any potential errors during initialization. -// pub fn new(texture_creator: &'static TextureCreator) -> GameResult { -// let map = Map::new(RAW_BOARD)?; - -// let start_node = map.start_positions.pacman; - -// let atlas_bytes = get_asset_bytes(Asset::Atlas)?; -// let atlas_texture = texture_creator.load_texture_bytes(&atlas_bytes).map_err(|e| { -// if e.to_string().contains("format") || e.to_string().contains("unsupported") { -// GameError::Texture(TextureError::InvalidFormat(format!("Unsupported texture format: {e}"))) -// } else { -// GameError::Texture(TextureError::LoadFailed(e.to_string())) -// } -// })?; - -// let atlas_mapper = AtlasMapper { -// frames: ATLAS_FRAMES.into_iter().map(|(k, v)| (k.to_string(), *v)).collect(), -// }; -// let atlas = SpriteAtlas::new(atlas_texture, atlas_mapper); - -// let text_texture = TextTexture::new(1.0); -// let audio = Audio::new(); -// let pacman = Pacman::new(&map.graph, start_node, &atlas)?; - -// // Generate items (pellets and energizers) -// let items = map.generate_items(&atlas)?; - -// // Initialize collision system -// let mut collision_system = CollisionSystem::default(); - -// // Register Pac-Man -// let pacman_id = collision_system.register_entity(pacman.position()); - -// // Register items -// let item_ids = items -// .iter() -// .map(|item| collision_system.register_entity(item.position())) -// .collect(); - -// // Create and register ghosts -// let ghosts = [GhostType::Blinky, GhostType::Pinky, GhostType::Inky, GhostType::Clyde] -// .iter() -// .zip( -// [ -// map.start_positions.blinky, -// map.start_positions.pinky, -// map.start_positions.inky, -// map.start_positions.clyde, -// ] -// .iter(), -// ) -// .map(|(ghost_type, start_node)| Ghost::new(&map.graph, *start_node, *ghost_type, &atlas)) -// .collect::>>()?; - -// // Register ghosts -// let ghost_ids = ghosts -// .iter() -// .map(|ghost| collision_system.register_entity(ghost.position())) -// .collect(); - -// Ok(Self { -// paused: false, -// map, -// atlas, -// pacman, -// pacman_id, -// ghosts, -// ghost_ids, -// items, -// item_ids, -// text_texture, -// audio, -// score: 0, -// debug_mode: false, -// collision_system, -// map_texture: None, -// map_rendered: false, -// texture_creator, -// event_queue: VecDeque::new(), -// }) -// } -// } diff --git a/src/helpers.rs b/src/helpers.rs deleted file mode 100644 index 4205194..0000000 --- a/src/helpers.rs +++ /dev/null @@ -1,10 +0,0 @@ -use glam::{IVec2, UVec2}; -use sdl2::rect::Rect; - -pub fn centered_with_size(pixel_pos: IVec2, size: UVec2) -> Rect { - // Ensure the position doesn't cause integer overflow when centering - let x = pixel_pos.x.saturating_sub(size.x as i32 / 2); - let y = pixel_pos.y.saturating_sub(size.y as i32 / 2); - - Rect::new(x, y, size.x, size.y) -} diff --git a/src/lib.rs b/src/lib.rs index 3668b97..e63462d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,11 +4,9 @@ pub mod app; pub mod asset; pub mod audio; pub mod constants; -pub mod entity; pub mod error; pub mod events; pub mod game; -pub mod helpers; pub mod map; pub mod platform; pub mod systems; diff --git a/src/main.rs b/src/main.rs index 907a875..5c31e3e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,11 +10,9 @@ mod asset; mod audio; mod constants; -mod entity; mod error; mod events; mod game; -mod helpers; mod map; mod platform; mod systems; diff --git a/src/map/builder.rs b/src/map/builder.rs index 2841230..1b2d993 100644 --- a/src/map/builder.rs +++ b/src/map/builder.rs @@ -1,7 +1,7 @@ //! Map construction and building functionality. use crate::constants::{MapTile, BOARD_CELL_SIZE, CELL_SIZE}; -use crate::entity::direction::Direction; -use crate::entity::graph::{Graph, Node, TraversalFlags}; +use crate::map::direction::Direction; +use crate::map::graph::{Graph, Node, TraversalFlags}; use crate::map::parser::MapTileParser; use crate::systems::movement::NodeId; use bevy_ecs::resource::Resource; diff --git a/src/entity/direction.rs b/src/map/direction.rs similarity index 100% rename from src/entity/direction.rs rename to src/map/direction.rs diff --git a/src/entity/graph.rs b/src/map/graph.rs similarity index 100% rename from src/entity/graph.rs rename to src/map/graph.rs diff --git a/src/map/mod.rs b/src/map/mod.rs index 8f2e2bc..e104058 100644 --- a/src/map/mod.rs +++ b/src/map/mod.rs @@ -1,6 +1,8 @@ //! This module defines the game map and provides functions for interacting with it. pub mod builder; +pub mod direction; +pub mod graph; pub mod layout; pub mod parser; pub mod render; diff --git a/src/systems/components.rs b/src/systems/components.rs index 5a9d4d7..ab86cb4 100644 --- a/src/systems/components.rs +++ b/src/systems/components.rs @@ -2,7 +2,7 @@ use bevy_ecs::{bundle::Bundle, component::Component, resource::Resource}; use bitflags::bitflags; use crate::{ - entity::graph::TraversalFlags, + map::graph::TraversalFlags, systems::movement::{BufferedDirection, Position, Velocity}, texture::{animated::AnimatedTexture, sprite::AtlasTile}, }; @@ -41,6 +41,7 @@ impl Ghost { } /// Returns the ghost's color for debug rendering. + #[allow(dead_code)] pub fn debug_color(&self) -> sdl2::pixels::Color { match self { Ghost::Blinky => sdl2::pixels::Color::RGB(255, 0, 0), // Red diff --git a/src/systems/ghost.rs b/src/systems/ghost.rs index bd96b17..60aaf47 100644 --- a/src/systems/ghost.rs +++ b/src/systems/ghost.rs @@ -3,8 +3,11 @@ use rand::prelude::*; use smallvec::SmallVec; use crate::{ - entity::{direction::Direction, graph::Edge}, - map::builder::Map, + map::{ + builder::Map, + direction::Direction, + graph::{Edge, TraversalFlags}, + }, systems::{ components::{DeltaTime, Ghost}, movement::{Position, Velocity}, @@ -32,9 +35,7 @@ pub fn ghost_movement_system( // Collect all available directions that ghosts can traverse for edge in Direction::DIRECTIONS.iter().flat_map(|d| intersection.get(*d)) { - if edge.traversal_flags.contains(crate::entity::graph::TraversalFlags::GHOST) - && edge.direction != opposite - { + if edge.traversal_flags.contains(TraversalFlags::GHOST) && edge.direction != opposite { non_opposite_options.push(edge); } } diff --git a/src/systems/input.rs b/src/systems/input.rs index 4ec59ca..f63702d 100644 --- a/src/systems/input.rs +++ b/src/systems/input.rs @@ -10,8 +10,8 @@ use sdl2::{event::Event, keyboard::Keycode, EventPump}; use crate::systems::components::DeltaTime; use crate::{ - entity::direction::Direction, events::{GameCommand, GameEvent}, + map::direction::Direction, }; #[derive(Resource, Default, Debug, Copy, Clone)] diff --git a/src/systems/movement.rs b/src/systems/movement.rs index 60195b4..e5c3767 100644 --- a/src/systems/movement.rs +++ b/src/systems/movement.rs @@ -1,6 +1,6 @@ -use crate::entity::direction::Direction; -use crate::entity::graph::Graph; use crate::error::{EntityError, GameResult}; +use crate::map::direction::Direction; +use crate::map::graph::Graph; use bevy_ecs::component::Component; use glam::Vec2; diff --git a/src/systems/player.rs b/src/systems/player.rs index 6673c4a..fac3e18 100644 --- a/src/systems/player.rs +++ b/src/systems/player.rs @@ -6,10 +6,10 @@ use bevy_ecs::{ }; use crate::{ - entity::graph::Edge, error::GameError, events::{GameCommand, GameEvent}, map::builder::Map, + map::graph::Edge, systems::{ components::{AudioState, DeltaTime, EntityType, GlobalState, PlayerControlled}, debug::DebugState, diff --git a/src/systems/render.rs b/src/systems/render.rs index 84b54c8..20d5ac9 100644 --- a/src/systems/render.rs +++ b/src/systems/render.rs @@ -7,6 +7,7 @@ use bevy_ecs::entity::Entity; use bevy_ecs::event::EventWriter; use bevy_ecs::prelude::{Changed, Or, RemovedComponents}; use bevy_ecs::system::{NonSendMut, Query, Res, ResMut}; +use sdl2::rect::{Point, Rect}; use sdl2::render::{Canvas, Texture}; use sdl2::video::Window; @@ -99,9 +100,10 @@ pub fn render_system( let pos = position.get_pixel_position(&map.graph); match pos { Ok(pos) => { - let dest = crate::helpers::centered_with_size( - glam::IVec2::new(pos.x as i32, pos.y as i32), - glam::UVec2::new(renderable.sprite.size.x as u32, renderable.sprite.size.y as u32), + let dest = Rect::from_center( + Point::from((pos.x as i32, pos.y as i32)), + renderable.sprite.size.x as u32, + renderable.sprite.size.y as u32, ); renderable diff --git a/tests/direction.rs b/tests/direction.rs index a7e36b2..6e9f1a0 100644 --- a/tests/direction.rs +++ b/tests/direction.rs @@ -1,5 +1,5 @@ use glam::IVec2; -use pacman::entity::direction::*; +use pacman::map::direction::*; #[test] fn test_direction_opposite() { diff --git a/tests/graph.rs b/tests/graph.rs index 7f4fc06..04e78d6 100644 --- a/tests/graph.rs +++ b/tests/graph.rs @@ -1,5 +1,5 @@ -use pacman::entity::direction::Direction; -use pacman::entity::graph::{Graph, Node, TraversalFlags}; +use pacman::map::direction::Direction; +use pacman::map::graph::{Graph, Node, TraversalFlags}; fn create_test_graph() -> Graph { let mut graph = Graph::new(); diff --git a/tests/helpers.rs b/tests/helpers.rs deleted file mode 100644 index 6831dff..0000000 --- a/tests/helpers.rs +++ /dev/null @@ -1,19 +0,0 @@ -use glam::{IVec2, UVec2}; -use pacman::helpers::centered_with_size; - -#[test] -fn test_centered_with_size() { - let test_cases = [ - ((100, 100), (50, 30), (75, 85)), - ((50, 50), (51, 31), (25, 35)), - ((0, 0), (100, 100), (-50, -50)), - ((-100, -50), (80, 40), (-140, -70)), - ((1000, 1000), (1000, 1000), (500, 500)), - ]; - - for ((pos_x, pos_y), (size_x, size_y), (expected_x, expected_y)) in test_cases { - let rect = centered_with_size(IVec2::new(pos_x, pos_y), UVec2::new(size_x, size_y)); - assert_eq!(rect.origin(), (expected_x, expected_y)); - assert_eq!(rect.size(), (size_x, size_y)); - } -} diff --git a/tests/item.rs b/tests/item.rs deleted file mode 100644 index c8f5b99..0000000 --- a/tests/item.rs +++ /dev/null @@ -1,46 +0,0 @@ -// use glam::U16Vec2; -// use pacman::texture::sprite::{AtlasTile, Sprite}; - -// #[test] -// fn test_item_type_get_score() { -// assert_eq!(ItemType::Pellet.get_score(), 10); -// assert_eq!(ItemType::Energizer.get_score(), 50); - -// let fruit = ItemType::Fruit { kind: FruitKind::Apple }; -// assert_eq!(fruit.get_score(), 100); -// } - -// #[test] -// fn test_fruit_kind_increasing_score() { -// // Build a list of fruit kinds, sorted by their index -// let mut kinds = FruitKind::iter() -// .map(|kind| (kind.index(), kind.get_score())) -// .collect::>(); -// kinds.sort_unstable_by_key(|(index, _)| *index); - -// assert_eq!(kinds.len(), FruitKind::COUNT); - -// // Check that the score increases as expected -// for window in kinds.windows(2) { -// let ((_, prev), (_, next)) = (window[0], window[1]); -// assert!(prev < next, "Fruits should have increasing scores, but {prev:?} < {next:?}"); -// } -// } - -// #[test] -// fn test_item_creation_and_collection() { -// let atlas_tile = AtlasTile { -// pos: U16Vec2::new(0, 0), -// size: U16Vec2::new(16, 16), -// color: None, -// }; -// let sprite = Sprite::new(atlas_tile); -// let mut item = Item::new(0, ItemType::Pellet, sprite); - -// assert!(!item.is_collected()); -// assert_eq!(item.get_score(), 10); -// assert_eq!(item.position().from_node_id(), 0); - -// item.collect(); -// assert!(item.is_collected()); -// }