mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-06 17:15:47 -06:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9624bcf359 | ||
|
|
67a5c4a1ed | ||
|
|
8b5e66f514 |
@@ -251,14 +251,13 @@ impl Game {
|
||||
backbuffer: NonSendMut<BackbufferResource>,
|
||||
debug_state: Res<DebugState>,
|
||||
mut dirty: ResMut<RenderDirty>| {
|
||||
if dirty.0 || *debug_state != DebugState::Off {
|
||||
if dirty.0 || debug_state.enabled {
|
||||
// Only copy backbuffer to main canvas if debug rendering is off
|
||||
// (debug rendering draws directly to main canvas)
|
||||
if *debug_state == DebugState::Off {
|
||||
canvas.copy(&backbuffer.0, None, None).unwrap();
|
||||
if !debug_state.enabled {
|
||||
canvas.present();
|
||||
}
|
||||
dirty.0 = false;
|
||||
canvas.present();
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
@@ -6,9 +6,28 @@ use bevy_ecs::system::{Query, Res};
|
||||
use crate::error::GameError;
|
||||
use crate::events::GameEvent;
|
||||
use crate::map::builder::Map;
|
||||
use crate::systems::components::{Collider, ItemCollider, PacmanCollider};
|
||||
use crate::systems::components::{Collider, GhostCollider, ItemCollider, PacmanCollider};
|
||||
use crate::systems::movement::Position;
|
||||
|
||||
/// Helper function to check collision between two entities with colliders.
|
||||
pub fn check_collision(
|
||||
pos1: &Position,
|
||||
collider1: &Collider,
|
||||
pos2: &Position,
|
||||
collider2: &Collider,
|
||||
map: &Map,
|
||||
) -> Result<bool, GameError> {
|
||||
let pixel1 = pos1
|
||||
.get_pixel_position(&map.graph)
|
||||
.map_err(|e| GameError::InvalidState(format!("Failed to get pixel position for entity 1: {}", e)))?;
|
||||
let pixel2 = pos2
|
||||
.get_pixel_position(&map.graph)
|
||||
.map_err(|e| GameError::InvalidState(format!("Failed to get pixel position for entity 2: {}", e)))?;
|
||||
|
||||
let distance = pixel1.distance(pixel2);
|
||||
Ok(collider1.collides_with(collider2.size, distance))
|
||||
}
|
||||
|
||||
/// Detects overlapping entities and generates collision events for gameplay systems.
|
||||
///
|
||||
/// Performs distance-based collision detection between Pac-Man and collectible items
|
||||
@@ -16,42 +35,49 @@ use crate::systems::movement::Position;
|
||||
/// a `GameEvent::Collision` for the item system to handle scoring and removal.
|
||||
/// Collision detection accounts for both entities being in motion and supports
|
||||
/// circular collision boundaries for accurate gameplay feel.
|
||||
///
|
||||
/// Also detects collisions between Pac-Man and ghosts for gameplay mechanics like
|
||||
/// power pellet effects, ghost eating, and player death.
|
||||
pub fn collision_system(
|
||||
map: Res<Map>,
|
||||
pacman_query: Query<(Entity, &Position, &Collider), With<PacmanCollider>>,
|
||||
item_query: Query<(Entity, &Position, &Collider), With<ItemCollider>>,
|
||||
ghost_query: Query<(Entity, &Position, &Collider), With<GhostCollider>>,
|
||||
mut events: EventWriter<GameEvent>,
|
||||
mut errors: EventWriter<GameError>,
|
||||
) {
|
||||
// Check PACMAN × ITEM collisions
|
||||
for (pacman_entity, pacman_pos, pacman_collider) in pacman_query.iter() {
|
||||
for (item_entity, item_pos, item_collider) in item_query.iter() {
|
||||
match (
|
||||
pacman_pos.get_pixel_position(&map.graph),
|
||||
item_pos.get_pixel_position(&map.graph),
|
||||
) {
|
||||
(Ok(pacman_pixel), Ok(item_pixel)) => {
|
||||
// Calculate the distance between the two entities's precise pixel positions
|
||||
let distance = pacman_pixel.distance(item_pixel);
|
||||
// Calculate the distance at which the two entities will collide
|
||||
let collision_distance = (pacman_collider.size + item_collider.size) / 2.0;
|
||||
|
||||
// If the distance between the two entities is less than the collision distance, then the two entities are colliding
|
||||
if distance < collision_distance {
|
||||
match check_collision(pacman_pos, pacman_collider, item_pos, item_collider, &map) {
|
||||
Ok(colliding) => {
|
||||
if colliding {
|
||||
events.write(GameEvent::Collision(pacman_entity, item_entity));
|
||||
}
|
||||
}
|
||||
// Either or both of the pixel positions failed to get, so we need to report the error
|
||||
(result_a, result_b) => {
|
||||
for result in [result_a, result_b] {
|
||||
if let Err(e) = result {
|
||||
errors.write(GameError::InvalidState(format!(
|
||||
"Collision system failed to get pixel positions for entities {:?} and {:?}: {}",
|
||||
pacman_entity, item_entity, e
|
||||
)));
|
||||
}
|
||||
Err(e) => {
|
||||
errors.write(GameError::InvalidState(format!(
|
||||
"Collision system failed to check collision between entities {:?} and {:?}: {}",
|
||||
pacman_entity, item_entity, e
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check PACMAN × GHOST collisions
|
||||
for (ghost_entity, ghost_pos, ghost_collider) in ghost_query.iter() {
|
||||
match check_collision(pacman_pos, pacman_collider, ghost_pos, ghost_collider, &map) {
|
||||
Ok(colliding) => {
|
||||
if colliding {
|
||||
events.write(GameEvent::Collision(pacman_entity, ghost_entity));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
errors.write(GameError::InvalidState(format!(
|
||||
"Collision system failed to check collision between entities {:?} and {:?}: {}",
|
||||
pacman_entity, ghost_entity, e
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +114,14 @@ pub struct Collider {
|
||||
pub size: f32,
|
||||
}
|
||||
|
||||
impl Collider {
|
||||
/// Checks if this collider collides with another collider at the given distance.
|
||||
pub fn collides_with(&self, other_size: f32, distance: f32) -> bool {
|
||||
let collision_distance = (self.size + other_size) / 2.0;
|
||||
distance < collision_distance
|
||||
}
|
||||
}
|
||||
|
||||
/// Marker components for collision filtering optimization
|
||||
#[derive(Component)]
|
||||
pub struct PacmanCollider;
|
||||
|
||||
@@ -16,22 +16,13 @@ use sdl2::render::{Canvas, Texture, TextureCreator};
|
||||
use sdl2::ttf::Font;
|
||||
use sdl2::video::{Window, WindowContext};
|
||||
|
||||
#[derive(Resource, Default, Debug, Copy, Clone, PartialEq)]
|
||||
pub enum DebugState {
|
||||
#[default]
|
||||
Off,
|
||||
Graph,
|
||||
Collision,
|
||||
#[derive(Resource, Default, Debug, Copy, Clone)]
|
||||
pub struct DebugState {
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
impl DebugState {
|
||||
pub fn next(&self) -> Self {
|
||||
match self {
|
||||
DebugState::Off => DebugState::Graph,
|
||||
DebugState::Graph => DebugState::Collision,
|
||||
DebugState::Collision => DebugState::Off,
|
||||
}
|
||||
}
|
||||
fn f32_to_u8(value: f32) -> u8 {
|
||||
(value * 255.0) as u8
|
||||
}
|
||||
|
||||
/// Resource to hold the debug texture for persistent rendering
|
||||
@@ -110,7 +101,7 @@ pub fn debug_render_system(
|
||||
colliders: Query<(&Collider, &Position)>,
|
||||
cursor: Res<CursorPosition>,
|
||||
) {
|
||||
if *debug_state == DebugState::Off {
|
||||
if !debug_state.enabled {
|
||||
return;
|
||||
}
|
||||
let scale =
|
||||
@@ -140,79 +131,86 @@ pub fn debug_render_system(
|
||||
// Draw debug info on the high-resolution debug texture
|
||||
canvas
|
||||
.with_texture_canvas(&mut debug_texture.0, |debug_canvas| {
|
||||
match *debug_state {
|
||||
DebugState::Graph => {
|
||||
// Find the closest node to the cursor
|
||||
// Find the closest node to the cursor
|
||||
|
||||
let closest_node = if let Some(cursor_world_pos) = cursor_world_pos {
|
||||
map.graph
|
||||
.nodes()
|
||||
.map(|node| node.position.distance(cursor_world_pos))
|
||||
.enumerate()
|
||||
.min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(Ordering::Less))
|
||||
.map(|(id, _)| id)
|
||||
let closest_node = if let Some(cursor_world_pos) = cursor_world_pos {
|
||||
map.graph
|
||||
.nodes()
|
||||
.map(|node| node.position.distance(cursor_world_pos))
|
||||
.enumerate()
|
||||
.min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(Ordering::Less))
|
||||
.map(|(id, _)| id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
debug_canvas.set_draw_color(Color::GREEN);
|
||||
for (collider, position) in colliders.iter() {
|
||||
let pos = position.get_pixel_position(&map.graph).unwrap();
|
||||
|
||||
// Transform position and size using common methods
|
||||
let pos = (pos * scale).as_ivec2();
|
||||
let size = (collider.size * scale) as u32;
|
||||
|
||||
let rect = Rect::from_center(Point::from((pos.x, pos.y)), size, size);
|
||||
debug_canvas.draw_rect(rect).unwrap();
|
||||
}
|
||||
|
||||
debug_canvas.set_draw_color(Color {
|
||||
a: f32_to_u8(0.4),
|
||||
..Color::RED
|
||||
});
|
||||
debug_canvas.set_blend_mode(sdl2::render::BlendMode::Blend);
|
||||
for (start_node, end_node) in map.graph.edges() {
|
||||
let start_node_model = map.graph.get_node(start_node).unwrap();
|
||||
let end_node = map.graph.get_node(end_node.target).unwrap().position;
|
||||
|
||||
// Transform positions using common method
|
||||
let start = transform_position_with_offset(start_node_model.position, scale);
|
||||
let end = transform_position_with_offset(end_node, scale);
|
||||
|
||||
debug_canvas
|
||||
.draw_line(Point::from((start.x, start.y)), Point::from((end.x, end.y)))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
for (id, node) in map.graph.nodes().enumerate() {
|
||||
let pos = node.position;
|
||||
|
||||
// Set color based on whether the node is the closest to the cursor
|
||||
debug_canvas.set_draw_color(Color {
|
||||
a: f32_to_u8(if Some(id) == closest_node { 0.75 } else { 0.6 }),
|
||||
..(if Some(id) == closest_node {
|
||||
Color::YELLOW
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Color::BLUE
|
||||
})
|
||||
});
|
||||
|
||||
debug_canvas.set_draw_color(Color::RED);
|
||||
for (start_node, end_node) in map.graph.edges() {
|
||||
let start_node_model = map.graph.get_node(start_node).unwrap();
|
||||
let end_node = map.graph.get_node(end_node.target).unwrap().position;
|
||||
// Transform position using common method
|
||||
let pos = transform_position_with_offset(pos, scale);
|
||||
let size = (2.0 * scale) as u32;
|
||||
|
||||
// Transform positions using common method
|
||||
let start = transform_position_with_offset(start_node_model.position, scale);
|
||||
let end = transform_position_with_offset(end_node, scale);
|
||||
debug_canvas
|
||||
.fill_rect(Rect::new(pos.x - (size as i32 / 2), pos.y - (size as i32 / 2), size, size))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
debug_canvas
|
||||
.draw_line(Point::from((start.x, start.y)), Point::from((end.x, end.y)))
|
||||
.unwrap();
|
||||
}
|
||||
// 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 pos = transform_position_with_offset(node.position, scale);
|
||||
|
||||
for (id, node) in map.graph.nodes().enumerate() {
|
||||
let pos = node.position;
|
||||
|
||||
// Set color based on whether the node is the closest to the cursor
|
||||
debug_canvas.set_draw_color(if Some(id) == closest_node {
|
||||
Color::YELLOW
|
||||
} else {
|
||||
Color::BLUE
|
||||
});
|
||||
|
||||
// Transform position using common method
|
||||
let pos = transform_position_with_offset(pos, scale);
|
||||
let size = (3.0 * scale) as u32;
|
||||
|
||||
debug_canvas
|
||||
.fill_rect(Rect::new(pos.x - (size as i32 / 2), pos.y - (size as i32 / 2), size, size))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// 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 pos = transform_position_with_offset(node.position, scale);
|
||||
|
||||
let surface = font.render(&closest_node_id.to_string()).blended(Color::WHITE).unwrap();
|
||||
let texture = texture_creator.create_texture_from_surface(&surface).unwrap();
|
||||
let dest = Rect::new(pos.x + 10, pos.y - 5, texture.query().width, texture.query().height);
|
||||
debug_canvas.copy(&texture, None, dest).unwrap();
|
||||
}
|
||||
}
|
||||
DebugState::Collision => {
|
||||
debug_canvas.set_draw_color(Color::GREEN);
|
||||
for (collider, position) in colliders.iter() {
|
||||
let pos = position.get_pixel_position(&map.graph).unwrap();
|
||||
|
||||
// Transform position and size using common methods
|
||||
let pos = (pos * scale).as_ivec2();
|
||||
let size = (collider.size * scale) as u32;
|
||||
|
||||
let rect = Rect::from_center(Point::from((pos.x, pos.y)), size, size);
|
||||
debug_canvas.draw_rect(rect).unwrap();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
let surface = font
|
||||
.render(&closest_node_id.to_string())
|
||||
.blended(Color {
|
||||
a: f32_to_u8(0.4),
|
||||
..Color::WHITE
|
||||
})
|
||||
.unwrap();
|
||||
let texture = texture_creator.create_texture_from_surface(&surface).unwrap();
|
||||
let dest = Rect::new(pos.x + 10, pos.y - 5, texture.query().width, texture.query().height);
|
||||
debug_canvas.copy(&texture, None, dest).unwrap();
|
||||
}
|
||||
|
||||
// Render timing information in the top-left corner
|
||||
@@ -222,4 +220,5 @@ pub fn debug_render_system(
|
||||
|
||||
// Draw the debug texture directly onto the main canvas at full resolution
|
||||
canvas.copy(&debug_texture.0, None, None).unwrap();
|
||||
canvas.present();
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ pub fn player_control_system(
|
||||
state.exit = true;
|
||||
}
|
||||
GameCommand::ToggleDebug => {
|
||||
*debug_state = debug_state.next();
|
||||
debug_state.enabled = !debug_state.enabled;
|
||||
}
|
||||
GameCommand::MuteAudio => {
|
||||
audio_state.muted = !audio_state.muted;
|
||||
|
||||
@@ -120,4 +120,6 @@ pub fn render_system(
|
||||
})
|
||||
.err()
|
||||
.map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into()));
|
||||
|
||||
canvas.copy(&backbuffer.0, None, None).unwrap();
|
||||
}
|
||||
|
||||
150
tests/collision.rs
Normal file
150
tests/collision.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
use bevy_ecs::{event::Events, prelude::*, system::RunSystemOnce, world::World};
|
||||
|
||||
use pacman::{
|
||||
error::GameError,
|
||||
events::GameEvent,
|
||||
map::builder::Map,
|
||||
systems::{
|
||||
collision::{check_collision, collision_system},
|
||||
components::{Collider, EntityType, Ghost, GhostCollider, ItemCollider, PacmanCollider},
|
||||
movement::Position,
|
||||
},
|
||||
};
|
||||
|
||||
fn create_test_world() -> World {
|
||||
let mut world = World::new();
|
||||
|
||||
// Add required resources
|
||||
world.insert_resource(Events::<GameEvent>::default());
|
||||
world.insert_resource(Events::<GameError>::default());
|
||||
|
||||
// Add a minimal test map
|
||||
world.insert_resource(create_test_map());
|
||||
|
||||
world
|
||||
}
|
||||
|
||||
fn create_test_map() -> Map {
|
||||
use pacman::constants::RAW_BOARD;
|
||||
Map::new(RAW_BOARD).expect("Failed to create test map")
|
||||
}
|
||||
|
||||
fn spawn_test_pacman(world: &mut World) -> Entity {
|
||||
world
|
||||
.spawn((Position::Stopped { node: 0 }, Collider { size: 10.0 }, PacmanCollider))
|
||||
.id()
|
||||
}
|
||||
|
||||
fn spawn_test_item(world: &mut World) -> Entity {
|
||||
world
|
||||
.spawn((
|
||||
Position::Stopped { node: 0 },
|
||||
Collider { size: 8.0 },
|
||||
ItemCollider,
|
||||
EntityType::Pellet,
|
||||
))
|
||||
.id()
|
||||
}
|
||||
|
||||
fn spawn_test_ghost(world: &mut World) -> Entity {
|
||||
world
|
||||
.spawn((
|
||||
Position::Stopped { node: 0 },
|
||||
Collider { size: 12.0 },
|
||||
GhostCollider,
|
||||
Ghost::Blinky,
|
||||
EntityType::Ghost,
|
||||
))
|
||||
.id()
|
||||
}
|
||||
|
||||
fn spawn_test_ghost_at_node(world: &mut World, node: usize) -> Entity {
|
||||
world
|
||||
.spawn((
|
||||
Position::Stopped { node },
|
||||
Collider { size: 12.0 },
|
||||
GhostCollider,
|
||||
Ghost::Blinky,
|
||||
EntityType::Ghost,
|
||||
))
|
||||
.id()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collider_collision_detection() {
|
||||
let collider1 = Collider { size: 10.0 };
|
||||
let collider2 = Collider { size: 8.0 };
|
||||
|
||||
// Test collision detection
|
||||
assert!(collider1.collides_with(collider2.size, 5.0)); // Should collide (distance < 9.0)
|
||||
assert!(!collider1.collides_with(collider2.size, 15.0)); // Should not collide (distance > 9.0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_collision_helper() {
|
||||
let map = create_test_map();
|
||||
let pos1 = Position::Stopped { node: 0 };
|
||||
let pos2 = Position::Stopped { node: 0 }; // Same position
|
||||
let collider1 = Collider { size: 10.0 };
|
||||
let collider2 = Collider { size: 8.0 };
|
||||
|
||||
// Test collision at same position
|
||||
let result = check_collision(&pos1, &collider1, &pos2, &collider2, &map);
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap()); // Should collide at same position
|
||||
|
||||
// Test collision at different positions
|
||||
let pos3 = Position::Stopped { node: 1 }; // Different position
|
||||
let result = check_collision(&pos1, &collider1, &pos3, &collider2, &map);
|
||||
assert!(result.is_ok());
|
||||
// May or may not collide depending on actual node positions
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collision_system_pacman_item() {
|
||||
let mut world = create_test_world();
|
||||
let _pacman = spawn_test_pacman(&mut world);
|
||||
let _item = spawn_test_item(&mut world);
|
||||
|
||||
// Run collision system - should not panic
|
||||
world
|
||||
.run_system_once(collision_system)
|
||||
.expect("System should run successfully");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collision_system_pacman_ghost() {
|
||||
let mut world = create_test_world();
|
||||
let _pacman = spawn_test_pacman(&mut world);
|
||||
let _ghost = spawn_test_ghost(&mut world);
|
||||
|
||||
// Run collision system - should not panic
|
||||
world
|
||||
.run_system_once(collision_system)
|
||||
.expect("System should run successfully");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collision_system_no_collision() {
|
||||
let mut world = create_test_world();
|
||||
let _pacman = spawn_test_pacman(&mut world);
|
||||
let _ghost = spawn_test_ghost_at_node(&mut world, 1); // Different node
|
||||
|
||||
// Run collision system - should not panic
|
||||
world
|
||||
.run_system_once(collision_system)
|
||||
.expect("System should run successfully");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collision_system_multiple_entities() {
|
||||
let mut world = create_test_world();
|
||||
let _pacman = spawn_test_pacman(&mut world);
|
||||
let _item = spawn_test_item(&mut world);
|
||||
let _ghost = spawn_test_ghost(&mut world);
|
||||
|
||||
// Run collision system - should not panic
|
||||
world
|
||||
.run_system_once(collision_system)
|
||||
.expect("System should run successfully");
|
||||
}
|
||||
@@ -1,61 +1,6 @@
|
||||
use pacman::events::{GameCommand, GameEvent};
|
||||
use pacman::map::direction::Direction;
|
||||
|
||||
#[test]
|
||||
fn test_game_command_variants() {
|
||||
// Test that all GameCommand variants can be created
|
||||
let commands = [
|
||||
GameCommand::Exit,
|
||||
GameCommand::MovePlayer(Direction::Up),
|
||||
GameCommand::MovePlayer(Direction::Down),
|
||||
GameCommand::MovePlayer(Direction::Left),
|
||||
GameCommand::MovePlayer(Direction::Right),
|
||||
GameCommand::ToggleDebug,
|
||||
GameCommand::MuteAudio,
|
||||
GameCommand::ResetLevel,
|
||||
GameCommand::TogglePause,
|
||||
];
|
||||
|
||||
// Just verify they can be created and compared
|
||||
assert_eq!(commands.len(), 9);
|
||||
assert_eq!(commands[0], GameCommand::Exit);
|
||||
assert_eq!(commands[1], GameCommand::MovePlayer(Direction::Up));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_game_command_equality() {
|
||||
assert_eq!(GameCommand::Exit, GameCommand::Exit);
|
||||
assert_eq!(GameCommand::ToggleDebug, GameCommand::ToggleDebug);
|
||||
assert_eq!(
|
||||
GameCommand::MovePlayer(Direction::Left),
|
||||
GameCommand::MovePlayer(Direction::Left)
|
||||
);
|
||||
|
||||
assert_ne!(GameCommand::Exit, GameCommand::ToggleDebug);
|
||||
assert_ne!(
|
||||
GameCommand::MovePlayer(Direction::Left),
|
||||
GameCommand::MovePlayer(Direction::Right)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_game_event_variants() {
|
||||
let command_event = GameEvent::Command(GameCommand::Exit);
|
||||
let collision_event = GameEvent::Collision(bevy_ecs::entity::Entity::from_raw(1), bevy_ecs::entity::Entity::from_raw(2));
|
||||
|
||||
// Test that events can be created and compared
|
||||
assert_eq!(command_event, GameEvent::Command(GameCommand::Exit));
|
||||
assert_ne!(command_event, collision_event);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_game_command_to_game_event_conversion() {
|
||||
let command = GameCommand::ToggleDebug;
|
||||
let event: GameEvent = command.into();
|
||||
|
||||
assert_eq!(event, GameEvent::Command(GameCommand::ToggleDebug));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_game_command_to_game_event_conversion_all_variants() {
|
||||
let commands = vec![
|
||||
@@ -72,35 +17,3 @@ fn test_game_command_to_game_event_conversion_all_variants() {
|
||||
assert_eq!(event, GameEvent::Command(command));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_player_all_directions() {
|
||||
let directions = [Direction::Up, Direction::Down, Direction::Left, Direction::Right];
|
||||
|
||||
for direction in directions {
|
||||
let command = GameCommand::MovePlayer(direction);
|
||||
let event: GameEvent = command.into();
|
||||
|
||||
if let GameEvent::Command(GameCommand::MovePlayer(dir)) = event {
|
||||
assert_eq!(dir, direction);
|
||||
} else {
|
||||
panic!("Expected MovePlayer command with direction {:?}", direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_game_event_debug_format() {
|
||||
let event = GameEvent::Command(GameCommand::Exit);
|
||||
let debug_str = format!("{:?}", event);
|
||||
assert!(debug_str.contains("Command"));
|
||||
assert!(debug_str.contains("Exit"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_game_command_debug_format() {
|
||||
let command = GameCommand::MovePlayer(Direction::Left);
|
||||
let debug_str = format!("{:?}", command);
|
||||
assert!(debug_str.contains("MovePlayer"));
|
||||
assert!(debug_str.contains("Left"));
|
||||
}
|
||||
|
||||
@@ -119,13 +119,6 @@ fn test_format_timing_display_basic() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_timing_display_empty() {
|
||||
let timing_data = vec![];
|
||||
let formatted = format_timing_display(timing_data);
|
||||
assert!(formatted.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_timing_display_units() {
|
||||
let timing_data = vec![
|
||||
|
||||
@@ -16,7 +16,6 @@ fn test_calculate_score_for_item() {
|
||||
assert!(EntityType::Pellet.score_value() < EntityType::PowerPellet.score_value());
|
||||
assert!(EntityType::Pellet.score_value().is_some());
|
||||
assert!(EntityType::PowerPellet.score_value().is_some());
|
||||
assert!(EntityType::Pellet.score_value().unwrap() < EntityType::PowerPellet.score_value().unwrap());
|
||||
assert!(EntityType::Player.score_value().is_none());
|
||||
assert!(EntityType::Ghost.score_value().is_none());
|
||||
}
|
||||
@@ -194,28 +193,6 @@ fn test_item_system_ignores_non_item_collisions() {
|
||||
assert_eq!(ghost_count, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_item_system_wrong_collision_order() {
|
||||
let mut world = create_test_world();
|
||||
let pacman = spawn_test_pacman(&mut world);
|
||||
let pellet = spawn_test_item(&mut world, EntityType::Pellet);
|
||||
|
||||
// Send collision event with entities in reverse order
|
||||
send_collision_event(&mut world, pellet, pacman);
|
||||
|
||||
world.run_system_once(item_system).expect("System should run successfully");
|
||||
|
||||
// Should still work correctly
|
||||
let score = world.resource::<ScoreResource>();
|
||||
assert_eq!(score.0, 10);
|
||||
let pellet_count = world
|
||||
.query::<&EntityType>()
|
||||
.iter(&world)
|
||||
.filter(|&entity_type| matches!(entity_type, EntityType::Pellet))
|
||||
.count();
|
||||
assert_eq!(pellet_count, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_item_system_no_collision_events() {
|
||||
let mut world = create_test_world();
|
||||
|
||||
@@ -21,7 +21,7 @@ fn create_test_world() -> World {
|
||||
|
||||
// Add resources
|
||||
world.insert_resource(GlobalState { exit: false });
|
||||
world.insert_resource(DebugState::Off);
|
||||
world.insert_resource(DebugState::default());
|
||||
world.insert_resource(AudioState::default());
|
||||
world.insert_resource(DeltaTime(1.0 / 60.0)); // 60 FPS
|
||||
world.insert_resource(Events::<GameEvent>::default());
|
||||
@@ -222,7 +222,7 @@ fn test_player_control_system_toggle_debug() {
|
||||
|
||||
// Check that debug state changed
|
||||
let debug_state = world.resource::<DebugState>();
|
||||
assert_eq!(*debug_state, DebugState::Graph);
|
||||
assert_eq!(debug_state.enabled, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -565,7 +565,7 @@ fn test_player_state_persistence_across_systems() {
|
||||
let position = *query.single(&world).expect("Player should exist");
|
||||
|
||||
// Check that the state changes persisted individually
|
||||
assert_eq!(debug_state_after_toggle, DebugState::Graph, "Debug state should have toggled");
|
||||
assert_eq!(debug_state_after_toggle.enabled, true, "Debug state should have toggled");
|
||||
assert!(audio_muted_after_toggle, "Audio should be muted");
|
||||
|
||||
// Player position depends on actual map connectivity
|
||||
|
||||
Reference in New Issue
Block a user