mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-15 04:12:34 -06:00
feat: rewrite ghost/item collision eventing into trigger-based observer
This commit is contained in:
@@ -36,19 +36,17 @@ fn test_check_collision_helper() {
|
||||
|
||||
#[test]
|
||||
fn test_collision_system_pacman_item() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, mut schedule) = common::create_test_world();
|
||||
let _pacman = common::spawn_test_pacman(&mut world, 0);
|
||||
let _item = common::spawn_test_item(&mut world, 0, EntityType::Pellet);
|
||||
|
||||
// Run collision system - should not panic
|
||||
world
|
||||
.run_system_once(collision_system)
|
||||
.expect("System should run successfully");
|
||||
schedule.run(&mut world);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collision_system_pacman_ghost() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, _) = common::create_test_world();
|
||||
let _pacman = common::spawn_test_pacman(&mut world, 0);
|
||||
let _ghost = common::spawn_test_ghost(&mut world, 0, GhostState::Normal);
|
||||
|
||||
@@ -60,19 +58,17 @@ fn test_collision_system_pacman_ghost() {
|
||||
|
||||
#[test]
|
||||
fn test_collision_system_no_collision() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, mut schedule) = common::create_test_world();
|
||||
let _pacman = common::spawn_test_pacman(&mut world, 0);
|
||||
let _ghost = common::spawn_test_ghost(&mut world, 1, GhostState::Normal); // Different node
|
||||
|
||||
// Run collision system - should not panic
|
||||
world
|
||||
.run_system_once(collision_system)
|
||||
.expect("System should run successfully");
|
||||
schedule.run(&mut world);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collision_system_multiple_entities() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, _) = common::create_test_world();
|
||||
let _pacman = common::spawn_test_pacman(&mut world, 0);
|
||||
let _item = common::spawn_test_item(&mut world, 0, EntityType::Pellet);
|
||||
let _ghost = common::spawn_test_ghost(&mut world, 0, GhostState::Normal);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use bevy_ecs::{entity::Entity, event::Events, world::World};
|
||||
use bevy_ecs::{entity::Entity, event::Events, schedule::Schedule, world::World};
|
||||
use glam::{U16Vec2, Vec2};
|
||||
use pacman::{
|
||||
asset::{get_asset_bytes, Asset},
|
||||
constants::RAW_BOARD,
|
||||
events::GameEvent,
|
||||
events::{CollisionTrigger, GameEvent},
|
||||
game::ATLAS_FRAMES,
|
||||
map::{
|
||||
builder::Map,
|
||||
@@ -13,9 +13,9 @@ use pacman::{
|
||||
graph::{Graph, Node},
|
||||
},
|
||||
systems::{
|
||||
AudioEvent, AudioState, BufferedDirection, Collider, DebugState, DeltaTime, EntityType, Ghost, GhostCollider, GhostState,
|
||||
GlobalState, ItemCollider, MovementModifiers, PacmanCollider, PelletCount, PlayerControlled, Position, ScoreResource,
|
||||
Velocity,
|
||||
item_collision_observer, AudioEvent, AudioState, BufferedDirection, Collider, DebugState, DeltaTime, EntityType, Ghost,
|
||||
GhostCollider, GhostState, GlobalState, ItemCollider, MovementModifiers, PacmanCollider, PelletCount, PlayerControlled,
|
||||
Position, ScoreResource, Velocity,
|
||||
},
|
||||
texture::sprite::{AtlasMapper, AtlasTile, SpriteAtlas},
|
||||
};
|
||||
@@ -75,7 +75,7 @@ pub fn create_test_graph() -> Graph {
|
||||
}
|
||||
|
||||
/// Creates a basic test world with required resources for ECS systems
|
||||
pub fn create_test_world() -> World {
|
||||
pub fn create_test_world() -> (World, Schedule) {
|
||||
let mut world = World::new();
|
||||
|
||||
// Add required resources
|
||||
@@ -93,7 +93,11 @@ pub fn create_test_world() -> World {
|
||||
}); // 60 FPS
|
||||
world.insert_resource(create_test_map());
|
||||
|
||||
world
|
||||
let schedule = Schedule::default();
|
||||
|
||||
world.add_observer(item_collision_observer);
|
||||
|
||||
(world, schedule)
|
||||
}
|
||||
|
||||
/// Creates a test map using the default RAW_BOARD
|
||||
@@ -163,9 +167,8 @@ pub fn send_game_event(world: &mut World, event: GameEvent) {
|
||||
}
|
||||
|
||||
/// Sends a collision event between two entities
|
||||
pub fn send_collision_event(world: &mut World, entity1: Entity, entity2: Entity) {
|
||||
let mut events = world.resource_mut::<Events<GameEvent>>();
|
||||
events.send(GameEvent::Collision(entity1, entity2));
|
||||
pub fn trigger_collision(world: &mut World, event: CollisionTrigger) {
|
||||
world.trigger(event);
|
||||
}
|
||||
|
||||
/// Creates a mock atlas tile for testing
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use bevy_ecs::{entity::Entity, system::RunSystemOnce};
|
||||
use pacman::systems::{item_system, EntityType, GhostState, Position, ScoreResource};
|
||||
use bevy_ecs::entity::Entity;
|
||||
use pacman::{
|
||||
events::CollisionTrigger,
|
||||
systems::{EntityType, GhostState, Position, ScoreResource},
|
||||
};
|
||||
use speculoos::prelude::*;
|
||||
|
||||
mod common;
|
||||
@@ -26,18 +29,17 @@ fn test_is_collectible_item() {
|
||||
|
||||
#[test]
|
||||
fn test_item_system_pellet_collection() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, mut schedule) = common::create_test_world();
|
||||
let pacman = common::spawn_test_pacman(&mut world, 0);
|
||||
let pellet = common::spawn_test_item(&mut world, 1, EntityType::Pellet);
|
||||
|
||||
// Send collision event
|
||||
common::send_collision_event(&mut world, pacman, pellet);
|
||||
common::trigger_collision(&mut world, CollisionTrigger::ItemCollision { pacman, item: pellet });
|
||||
|
||||
// Run the item system
|
||||
world.run_system_once(item_system).expect("System should run successfully");
|
||||
schedule.run(&mut world);
|
||||
|
||||
// Check that score was updated
|
||||
let score = world.resource::<ScoreResource>();
|
||||
let score = world.resource_mut::<ScoreResource>();
|
||||
assert_that(&score.0).is_equal_to(10);
|
||||
|
||||
// Check that the pellet was despawned (query should return empty)
|
||||
@@ -51,13 +53,19 @@ fn test_item_system_pellet_collection() {
|
||||
|
||||
#[test]
|
||||
fn test_item_system_power_pellet_collection() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, mut schedule) = common::create_test_world();
|
||||
let pacman = common::spawn_test_pacman(&mut world, 0);
|
||||
let power_pellet = common::spawn_test_item(&mut world, 1, EntityType::PowerPellet);
|
||||
|
||||
common::send_collision_event(&mut world, pacman, power_pellet);
|
||||
common::trigger_collision(
|
||||
&mut world,
|
||||
CollisionTrigger::ItemCollision {
|
||||
pacman,
|
||||
item: power_pellet,
|
||||
},
|
||||
);
|
||||
|
||||
world.run_system_once(item_system).expect("System should run successfully");
|
||||
schedule.run(&mut world);
|
||||
|
||||
// Check that score was updated with power pellet value
|
||||
let score = world.resource::<ScoreResource>();
|
||||
@@ -74,23 +82,31 @@ fn test_item_system_power_pellet_collection() {
|
||||
|
||||
#[test]
|
||||
fn test_item_system_multiple_collections() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, mut schedule) = common::create_test_world();
|
||||
let pacman = common::spawn_test_pacman(&mut world, 0);
|
||||
let pellet1 = common::spawn_test_item(&mut world, 1, EntityType::Pellet);
|
||||
let pellet2 = common::spawn_test_item(&mut world, 2, EntityType::Pellet);
|
||||
let power_pellet = common::spawn_test_item(&mut world, 3, EntityType::PowerPellet);
|
||||
|
||||
// Send multiple collision events
|
||||
common::send_collision_event(&mut world, pacman, pellet1);
|
||||
common::send_collision_event(&mut world, pacman, pellet2);
|
||||
common::send_collision_event(&mut world, pacman, power_pellet);
|
||||
common::trigger_collision(&mut world, CollisionTrigger::ItemCollision { pacman, item: pellet1 });
|
||||
common::trigger_collision(&mut world, CollisionTrigger::ItemCollision { pacman, item: pellet2 });
|
||||
common::trigger_collision(
|
||||
&mut world,
|
||||
CollisionTrigger::ItemCollision {
|
||||
pacman,
|
||||
item: power_pellet,
|
||||
},
|
||||
);
|
||||
|
||||
world.run_system_once(item_system).expect("System should run successfully");
|
||||
schedule.run(&mut world);
|
||||
|
||||
// Check final score: 2 pellets (20) + 1 power pellet (50) = 70
|
||||
let score = world.resource::<ScoreResource>();
|
||||
assert_that(&score.0).is_equal_to(70);
|
||||
|
||||
schedule.run(&mut world);
|
||||
|
||||
// Check that all items were despawned
|
||||
let pellet_count = world
|
||||
.query::<&EntityType>()
|
||||
@@ -108,7 +124,7 @@ fn test_item_system_multiple_collections() {
|
||||
|
||||
#[test]
|
||||
fn test_item_system_ignores_non_item_collisions() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, mut schedule) = common::create_test_world();
|
||||
let pacman = common::spawn_test_pacman(&mut world, 0);
|
||||
|
||||
// Create a ghost entity (not an item)
|
||||
@@ -118,9 +134,9 @@ fn test_item_system_ignores_non_item_collisions() {
|
||||
let initial_score = world.resource::<ScoreResource>().0;
|
||||
|
||||
// Send collision event between pacman and ghost
|
||||
common::send_collision_event(&mut world, pacman, ghost);
|
||||
common::trigger_collision(&mut world, CollisionTrigger::ItemCollision { pacman, item: ghost });
|
||||
|
||||
world.run_system_once(item_system).expect("System should run successfully");
|
||||
schedule.run(&mut world);
|
||||
|
||||
// Score should remain unchanged
|
||||
let score = world.resource::<ScoreResource>();
|
||||
@@ -137,14 +153,14 @@ fn test_item_system_ignores_non_item_collisions() {
|
||||
|
||||
#[test]
|
||||
fn test_item_system_no_collision_events() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, mut schedule) = common::create_test_world();
|
||||
let _pacman = common::spawn_test_pacman(&mut world, 0);
|
||||
let _pellet = common::spawn_test_item(&mut world, 1, EntityType::Pellet);
|
||||
|
||||
let initial_score = world.resource::<ScoreResource>().0;
|
||||
|
||||
// Run system without any collision events
|
||||
world.run_system_once(item_system).expect("System should run successfully");
|
||||
schedule.run(&mut world);
|
||||
|
||||
// Nothing should change
|
||||
let score = world.resource::<ScoreResource>();
|
||||
@@ -159,19 +175,22 @@ fn test_item_system_no_collision_events() {
|
||||
|
||||
#[test]
|
||||
fn test_item_system_collision_with_missing_entity() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, mut schedule) = common::create_test_world();
|
||||
let pacman = common::spawn_test_pacman(&mut world, 0);
|
||||
|
||||
// Create a fake entity ID that doesn't exist
|
||||
let fake_entity = Entity::from_raw(999);
|
||||
|
||||
common::send_collision_event(&mut world, pacman, fake_entity);
|
||||
common::trigger_collision(
|
||||
&mut world,
|
||||
CollisionTrigger::ItemCollision {
|
||||
pacman,
|
||||
item: fake_entity,
|
||||
},
|
||||
);
|
||||
|
||||
// System should handle gracefully and not crash
|
||||
world
|
||||
.run_system_once(item_system)
|
||||
.expect("System should handle missing entities gracefully");
|
||||
|
||||
schedule.run(&mut world);
|
||||
// Score should remain unchanged
|
||||
let score = world.resource::<ScoreResource>();
|
||||
assert_that(&score.0).is_equal_to(0);
|
||||
@@ -179,7 +198,7 @@ fn test_item_system_collision_with_missing_entity() {
|
||||
|
||||
#[test]
|
||||
fn test_item_system_preserves_existing_score() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, mut schedule) = common::create_test_world();
|
||||
|
||||
// Set initial score
|
||||
world.insert_resource(ScoreResource(100));
|
||||
@@ -187,9 +206,9 @@ fn test_item_system_preserves_existing_score() {
|
||||
let pacman = common::spawn_test_pacman(&mut world, 0);
|
||||
let pellet = common::spawn_test_item(&mut world, 1, EntityType::Pellet);
|
||||
|
||||
common::send_collision_event(&mut world, pacman, pellet);
|
||||
common::trigger_collision(&mut world, CollisionTrigger::ItemCollision { pacman, item: pellet });
|
||||
|
||||
world.run_system_once(item_system).expect("System should run successfully");
|
||||
schedule.run(&mut world);
|
||||
|
||||
// Score should be initial + pellet value
|
||||
let score = world.resource::<ScoreResource>();
|
||||
@@ -198,7 +217,7 @@ fn test_item_system_preserves_existing_score() {
|
||||
|
||||
#[test]
|
||||
fn test_power_pellet_does_not_affect_ghosts_in_eyes_state() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, mut schedule) = common::create_test_world();
|
||||
let pacman = common::spawn_test_pacman(&mut world, 0);
|
||||
let power_pellet = common::spawn_test_item(&mut world, 1, EntityType::PowerPellet);
|
||||
|
||||
@@ -208,9 +227,15 @@ fn test_power_pellet_does_not_affect_ghosts_in_eyes_state() {
|
||||
// Spawn a ghost in Normal state
|
||||
let normal_ghost = common::spawn_test_ghost(&mut world, 3, GhostState::Normal);
|
||||
|
||||
common::send_collision_event(&mut world, pacman, power_pellet);
|
||||
common::trigger_collision(
|
||||
&mut world,
|
||||
CollisionTrigger::ItemCollision {
|
||||
pacman,
|
||||
item: power_pellet,
|
||||
},
|
||||
);
|
||||
|
||||
world.run_system_once(item_system).expect("System should run successfully");
|
||||
schedule.run(&mut world);
|
||||
|
||||
// Check that the power pellet was collected and score updated
|
||||
let score = world.resource::<ScoreResource>();
|
||||
|
||||
@@ -112,7 +112,7 @@ fn test_entity_type_traversal_flags() {
|
||||
|
||||
#[test]
|
||||
fn test_player_control_system_move_command() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, _) = common::create_test_world();
|
||||
let _player = common::spawn_test_player(&mut world, 0);
|
||||
|
||||
// Send move command
|
||||
@@ -141,7 +141,7 @@ fn test_player_control_system_move_command() {
|
||||
|
||||
#[test]
|
||||
fn test_player_control_system_exit_command() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, _) = common::create_test_world();
|
||||
let _player = common::spawn_test_player(&mut world, 0);
|
||||
|
||||
// Send exit command
|
||||
@@ -159,7 +159,7 @@ fn test_player_control_system_exit_command() {
|
||||
|
||||
#[test]
|
||||
fn test_player_control_system_toggle_debug() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, _) = common::create_test_world();
|
||||
let _player = common::spawn_test_player(&mut world, 0);
|
||||
|
||||
// Send toggle debug command
|
||||
@@ -177,7 +177,7 @@ fn test_player_control_system_toggle_debug() {
|
||||
|
||||
#[test]
|
||||
fn test_player_control_system_mute_audio() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, _) = common::create_test_world();
|
||||
let _player = common::spawn_test_player(&mut world, 0);
|
||||
|
||||
// Send mute audio command
|
||||
@@ -206,7 +206,7 @@ fn test_player_control_system_mute_audio() {
|
||||
|
||||
#[test]
|
||||
fn test_player_control_system_no_player_entity() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, _) = common::create_test_world();
|
||||
// Don't spawn a player entity
|
||||
|
||||
common::send_game_event(&mut world, GameEvent::Command(GameCommand::MovePlayer(Direction::Up)));
|
||||
@@ -221,7 +221,7 @@ fn test_player_control_system_no_player_entity() {
|
||||
|
||||
#[test]
|
||||
fn test_player_movement_system_buffered_direction_expires() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, _) = common::create_test_world();
|
||||
let player = common::spawn_test_player(&mut world, 0);
|
||||
|
||||
// Set a buffered direction with short time
|
||||
@@ -251,7 +251,7 @@ fn test_player_movement_system_buffered_direction_expires() {
|
||||
|
||||
#[test]
|
||||
fn test_player_movement_system_start_moving_from_stopped() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, _) = common::create_test_world();
|
||||
let _player = common::spawn_test_player(&mut world, 0);
|
||||
|
||||
// Player starts at node 0, facing right (towards node 1)
|
||||
@@ -276,7 +276,7 @@ fn test_player_movement_system_start_moving_from_stopped() {
|
||||
|
||||
#[test]
|
||||
fn test_player_movement_system_buffered_direction_change() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, _) = common::create_test_world();
|
||||
let player = common::spawn_test_player(&mut world, 0);
|
||||
|
||||
// Set a buffered direction to go down (towards node 2)
|
||||
@@ -307,7 +307,7 @@ fn test_player_movement_system_buffered_direction_change() {
|
||||
|
||||
#[test]
|
||||
fn test_player_movement_system_no_valid_edge() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, _) = common::create_test_world();
|
||||
let player = common::spawn_test_player(&mut world, 0);
|
||||
|
||||
// Set velocity to direction with no edge
|
||||
@@ -332,7 +332,7 @@ fn test_player_movement_system_no_valid_edge() {
|
||||
|
||||
#[test]
|
||||
fn test_player_movement_system_continue_moving() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, _) = common::create_test_world();
|
||||
let player = common::spawn_test_player(&mut world, 0);
|
||||
|
||||
// Set player to already be moving
|
||||
@@ -362,7 +362,7 @@ fn test_player_movement_system_continue_moving() {
|
||||
|
||||
#[test]
|
||||
fn test_full_player_input_to_movement_flow() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, _) = common::create_test_world();
|
||||
let _player = common::spawn_test_player(&mut world, 0);
|
||||
|
||||
// Send move command
|
||||
@@ -396,7 +396,7 @@ fn test_full_player_input_to_movement_flow() {
|
||||
|
||||
#[test]
|
||||
fn test_buffered_direction_timing() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, _) = common::create_test_world();
|
||||
let _player = common::spawn_test_player(&mut world, 0);
|
||||
|
||||
// Send move command
|
||||
@@ -435,7 +435,7 @@ fn test_buffered_direction_timing() {
|
||||
|
||||
#[test]
|
||||
fn test_multiple_rapid_direction_changes() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, _) = common::create_test_world();
|
||||
let _player = common::spawn_test_player(&mut world, 0);
|
||||
|
||||
// Send multiple rapid direction changes
|
||||
@@ -468,7 +468,7 @@ fn test_multiple_rapid_direction_changes() {
|
||||
|
||||
#[test]
|
||||
fn test_player_state_persistence_across_systems() {
|
||||
let mut world = common::create_test_world();
|
||||
let (mut world, _) = common::create_test_world();
|
||||
let _player = common::spawn_test_player(&mut world, 0);
|
||||
|
||||
// Test that multiple commands can be processed - but need to handle events properly
|
||||
|
||||
Reference in New Issue
Block a user