mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-06 03:15:48 -06:00
test: add player control & movement system testing
This commit is contained in:
484
tests/player.rs
484
tests/player.rs
@@ -1,7 +1,64 @@
|
||||
use pacman::map::direction::Direction;
|
||||
use pacman::map::graph::{Edge, TraversalFlags};
|
||||
use pacman::systems::components::EntityType;
|
||||
use pacman::systems::player::can_traverse;
|
||||
use bevy_ecs::{event::Events, prelude::*, system::RunSystemOnce, world::World};
|
||||
|
||||
use pacman::{
|
||||
events::{GameCommand, GameEvent},
|
||||
map::{
|
||||
builder::Map,
|
||||
direction::Direction,
|
||||
graph::{Edge, TraversalFlags},
|
||||
},
|
||||
systems::{
|
||||
components::{AudioState, DeltaTime, EntityType, GlobalState, PlayerControlled},
|
||||
debug::DebugState,
|
||||
movement::{BufferedDirection, Position, Velocity},
|
||||
player::{can_traverse, player_control_system, player_movement_system},
|
||||
},
|
||||
};
|
||||
|
||||
// Test helper functions for ECS setup
|
||||
fn create_test_world() -> World {
|
||||
let mut world = World::new();
|
||||
|
||||
// Add resources
|
||||
world.insert_resource(GlobalState { exit: false });
|
||||
world.insert_resource(DebugState::Off);
|
||||
world.insert_resource(AudioState::default());
|
||||
world.insert_resource(DeltaTime(1.0 / 60.0)); // 60 FPS
|
||||
world.insert_resource(Events::<GameEvent>::default());
|
||||
world.insert_resource(Events::<pacman::error::GameError>::default());
|
||||
|
||||
// Create a simple test map with nodes and edges
|
||||
let test_map = create_test_map();
|
||||
world.insert_resource(test_map);
|
||||
|
||||
world
|
||||
}
|
||||
|
||||
fn create_test_map() -> Map {
|
||||
// Use the actual RAW_BOARD from constants.rs
|
||||
use pacman::constants::RAW_BOARD;
|
||||
Map::new(RAW_BOARD).expect("Failed to create test map")
|
||||
}
|
||||
|
||||
fn spawn_test_player(world: &mut World) -> Entity {
|
||||
world
|
||||
.spawn((
|
||||
PlayerControlled,
|
||||
Position::Stopped { node: 0 },
|
||||
Velocity {
|
||||
speed: 1.0,
|
||||
direction: Direction::Right,
|
||||
},
|
||||
BufferedDirection::None,
|
||||
EntityType::Player,
|
||||
))
|
||||
.id()
|
||||
}
|
||||
|
||||
fn send_game_event(world: &mut World, command: GameCommand) {
|
||||
let mut events = world.resource_mut::<Events<GameEvent>>();
|
||||
events.send(GameEvent::Command(command));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_can_traverse_player_on_all_edges() {
|
||||
@@ -98,3 +155,422 @@ fn test_entity_type_traversal_flags() {
|
||||
assert_eq!(EntityType::Pellet.traversal_flags(), TraversalFlags::empty());
|
||||
assert_eq!(EntityType::PowerPellet.traversal_flags(), TraversalFlags::empty());
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ECS System Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn test_player_control_system_move_command() {
|
||||
let mut world = create_test_world();
|
||||
let _player = spawn_test_player(&mut world);
|
||||
|
||||
// Send move command
|
||||
send_game_event(&mut world, GameCommand::MovePlayer(Direction::Up));
|
||||
|
||||
// Run the system
|
||||
world
|
||||
.run_system_once(player_control_system)
|
||||
.expect("System should run successfully");
|
||||
|
||||
// Check that buffered direction was updated
|
||||
let mut query = world.query::<&BufferedDirection>();
|
||||
let buffered_direction = query.single(&world).expect("Player should exist");
|
||||
|
||||
match *buffered_direction {
|
||||
BufferedDirection::Some {
|
||||
direction,
|
||||
remaining_time,
|
||||
} => {
|
||||
assert_eq!(direction, Direction::Up);
|
||||
assert_eq!(remaining_time, 0.25);
|
||||
}
|
||||
BufferedDirection::None => panic!("Expected buffered direction to be set"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_player_control_system_exit_command() {
|
||||
let mut world = create_test_world();
|
||||
let _player = spawn_test_player(&mut world);
|
||||
|
||||
// Send exit command
|
||||
send_game_event(&mut world, GameCommand::Exit);
|
||||
|
||||
// Run the system
|
||||
world
|
||||
.run_system_once(player_control_system)
|
||||
.expect("System should run successfully");
|
||||
|
||||
// Check that exit flag was set
|
||||
let state = world.resource::<GlobalState>();
|
||||
assert!(state.exit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_player_control_system_toggle_debug() {
|
||||
let mut world = create_test_world();
|
||||
let _player = spawn_test_player(&mut world);
|
||||
|
||||
// Send toggle debug command
|
||||
send_game_event(&mut world, GameCommand::ToggleDebug);
|
||||
|
||||
// Run the system
|
||||
world
|
||||
.run_system_once(player_control_system)
|
||||
.expect("System should run successfully");
|
||||
|
||||
// Check that debug state changed
|
||||
let debug_state = world.resource::<DebugState>();
|
||||
assert_eq!(*debug_state, DebugState::Graph);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_player_control_system_mute_audio() {
|
||||
let mut world = create_test_world();
|
||||
let _player = spawn_test_player(&mut world);
|
||||
|
||||
// Send mute audio command
|
||||
send_game_event(&mut world, GameCommand::MuteAudio);
|
||||
|
||||
// Run the system
|
||||
world
|
||||
.run_system_once(player_control_system)
|
||||
.expect("System should run successfully");
|
||||
|
||||
// Check that audio was muted
|
||||
let audio_state = world.resource::<AudioState>();
|
||||
assert!(audio_state.muted);
|
||||
|
||||
// Send mute audio command again to unmute - need fresh events
|
||||
world.resource_mut::<Events<GameEvent>>().clear(); // Clear previous events
|
||||
send_game_event(&mut world, GameCommand::MuteAudio);
|
||||
world
|
||||
.run_system_once(player_control_system)
|
||||
.expect("System should run successfully");
|
||||
|
||||
// Check that audio was unmuted
|
||||
let audio_state = world.resource::<AudioState>();
|
||||
assert!(!audio_state.muted, "Audio should be unmuted after second toggle");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_player_control_system_no_player_entity() {
|
||||
let mut world = create_test_world();
|
||||
// Don't spawn a player entity
|
||||
|
||||
send_game_event(&mut world, GameCommand::MovePlayer(Direction::Up));
|
||||
|
||||
// Run the system - should write an error
|
||||
world
|
||||
.run_system_once(player_control_system)
|
||||
.expect("System should run successfully");
|
||||
|
||||
// Check that an error was written (we can't easily check Events without manual management,
|
||||
// so for this test we just verify the system ran without panicking)
|
||||
// In a real implementation, you might expose error checking through the ECS world
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_player_movement_system_buffered_direction_expires() {
|
||||
let mut world = create_test_world();
|
||||
let player = spawn_test_player(&mut world);
|
||||
|
||||
// Set a buffered direction with short time
|
||||
world.entity_mut(player).insert(BufferedDirection::Some {
|
||||
direction: Direction::Up,
|
||||
remaining_time: 0.01, // Very short time
|
||||
});
|
||||
|
||||
// Set delta time to expire the buffered direction
|
||||
world.insert_resource(DeltaTime(0.02));
|
||||
|
||||
// Run the system
|
||||
world
|
||||
.run_system_once(player_movement_system)
|
||||
.expect("System should run successfully");
|
||||
|
||||
// Check that buffered direction expired or remaining time decreased significantly
|
||||
let mut query = world.query::<&BufferedDirection>();
|
||||
let buffered_direction = query.single(&world).expect("Player should exist");
|
||||
match *buffered_direction {
|
||||
BufferedDirection::None => {} // Expected - fully expired
|
||||
BufferedDirection::Some { remaining_time, .. } => {
|
||||
assert!(
|
||||
remaining_time <= 0.0,
|
||||
"Buffered direction should be expired or have non-positive time"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_player_movement_system_start_moving_from_stopped() {
|
||||
let mut world = create_test_world();
|
||||
let _player = spawn_test_player(&mut world);
|
||||
|
||||
// Player starts at node 0, facing right (towards node 1)
|
||||
// Should start moving when system runs
|
||||
|
||||
world
|
||||
.run_system_once(player_movement_system)
|
||||
.expect("System should run successfully");
|
||||
|
||||
// Check that player started moving
|
||||
let mut query = world.query::<&Position>();
|
||||
let position = query.single(&world).expect("Player should exist");
|
||||
|
||||
match *position {
|
||||
Position::Moving { from, .. } => {
|
||||
assert_eq!(from, 0, "Player should start from node 0");
|
||||
// Don't assert exact target node since the real map has different connectivity
|
||||
}
|
||||
Position::Stopped { .. } => {} // May stay stopped if no valid edge in current direction
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_player_movement_system_buffered_direction_change() {
|
||||
let mut world = create_test_world();
|
||||
let player = spawn_test_player(&mut world);
|
||||
|
||||
// Set a buffered direction to go down (towards node 2)
|
||||
world.entity_mut(player).insert(BufferedDirection::Some {
|
||||
direction: Direction::Down,
|
||||
remaining_time: 1.0,
|
||||
});
|
||||
|
||||
world
|
||||
.run_system_once(player_movement_system)
|
||||
.expect("System should run successfully");
|
||||
|
||||
// Check that player started moving down instead of right
|
||||
let mut query = world.query::<(&Position, &Velocity, &BufferedDirection)>();
|
||||
let (position, _velocity, _buffered_direction) = query.single(&world).expect("Player should exist");
|
||||
|
||||
match *position {
|
||||
Position::Moving { from, to, .. } => {
|
||||
assert_eq!(from, 0);
|
||||
assert_eq!(to, 2); // Should be moving to node 2 (down)
|
||||
}
|
||||
Position::Stopped { .. } => panic!("Player should have started moving"),
|
||||
}
|
||||
|
||||
// Check if the movement actually happened based on the real map connectivity
|
||||
// The buffered direction might not be consumed if there's no valid edge in that direction
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_player_movement_system_no_valid_edge() {
|
||||
let mut world = create_test_world();
|
||||
let player = spawn_test_player(&mut world);
|
||||
|
||||
// Set velocity to direction with no edge
|
||||
world.entity_mut(player).insert(Velocity {
|
||||
speed: 1.0,
|
||||
direction: Direction::Up, // No edge up from node 0
|
||||
});
|
||||
|
||||
world
|
||||
.run_system_once(player_movement_system)
|
||||
.expect("System should run successfully");
|
||||
|
||||
// Player should remain stopped
|
||||
let mut query = world.query::<&Position>();
|
||||
let position = query.single(&world).expect("Player should exist");
|
||||
|
||||
match *position {
|
||||
Position::Stopped { node } => assert_eq!(node, 0),
|
||||
Position::Moving { .. } => panic!("Player shouldn't be able to move without valid edge"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_player_movement_system_continue_moving() {
|
||||
let mut world = create_test_world();
|
||||
let player = spawn_test_player(&mut world);
|
||||
|
||||
// Set player to already be moving
|
||||
world.entity_mut(player).insert(Position::Moving {
|
||||
from: 0,
|
||||
to: 1,
|
||||
remaining_distance: 50.0,
|
||||
});
|
||||
|
||||
world
|
||||
.run_system_once(player_movement_system)
|
||||
.expect("System should run successfully");
|
||||
|
||||
// Check that player continued moving and distance decreased
|
||||
let mut query = world.query::<&Position>();
|
||||
let position = query.single(&world).expect("Player should exist");
|
||||
|
||||
match *position {
|
||||
Position::Moving { remaining_distance, .. } => {
|
||||
assert!(remaining_distance < 50.0); // Should have moved
|
||||
}
|
||||
Position::Stopped { .. } => {
|
||||
// If player reached destination, that's also valid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Integration Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn test_full_player_input_to_movement_flow() {
|
||||
let mut world = create_test_world();
|
||||
let _player = spawn_test_player(&mut world);
|
||||
|
||||
// Send move command
|
||||
send_game_event(&mut world, GameCommand::MovePlayer(Direction::Down));
|
||||
|
||||
// Run control system to process input
|
||||
world
|
||||
.run_system_once(player_control_system)
|
||||
.expect("System should run successfully");
|
||||
|
||||
// Run movement system to execute movement
|
||||
world
|
||||
.run_system_once(player_movement_system)
|
||||
.expect("System should run successfully");
|
||||
|
||||
// Check final state - player should be moving down
|
||||
let mut query = world.query::<(&Position, &Velocity, &BufferedDirection)>();
|
||||
let (position, _velocity, _buffered_direction) = query.single(&world).expect("Player should exist");
|
||||
|
||||
match *position {
|
||||
Position::Moving { from, to, .. } => {
|
||||
assert_eq!(from, 0);
|
||||
assert_eq!(to, 2); // Moving to node 2 (down)
|
||||
}
|
||||
Position::Stopped { .. } => panic!("Player should be moving"),
|
||||
}
|
||||
|
||||
// Check that player moved in the buffered direction if possible
|
||||
// In the real map, the buffered direction may not be consumable if there's no valid edge
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_buffered_direction_timing() {
|
||||
let mut world = create_test_world();
|
||||
let _player = spawn_test_player(&mut world);
|
||||
|
||||
// Send move command
|
||||
send_game_event(&mut world, GameCommand::MovePlayer(Direction::Up));
|
||||
world
|
||||
.run_system_once(player_control_system)
|
||||
.expect("System should run successfully");
|
||||
|
||||
// Run movement system multiple times with small delta times
|
||||
world.insert_resource(DeltaTime(0.1)); // 0.1 seconds
|
||||
|
||||
// First run - buffered direction should still be active
|
||||
world
|
||||
.run_system_once(player_movement_system)
|
||||
.expect("System should run successfully");
|
||||
let mut query = world.query::<&BufferedDirection>();
|
||||
let buffered_direction = query.single(&world).expect("Player should exist");
|
||||
|
||||
match *buffered_direction {
|
||||
BufferedDirection::Some { remaining_time, .. } => {
|
||||
assert!(remaining_time > 0.0);
|
||||
assert!(remaining_time < 0.25);
|
||||
}
|
||||
BufferedDirection::None => panic!("Buffered direction should still be active"),
|
||||
}
|
||||
|
||||
// Run again to fully expire the buffered direction
|
||||
world.insert_resource(DeltaTime(0.2)); // Total 0.3 seconds, should expire
|
||||
world
|
||||
.run_system_once(player_movement_system)
|
||||
.expect("System should run successfully");
|
||||
|
||||
let buffered_direction = query.single(&world).expect("Player should exist");
|
||||
assert_eq!(*buffered_direction, BufferedDirection::None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_rapid_direction_changes() {
|
||||
let mut world = create_test_world();
|
||||
let _player = spawn_test_player(&mut world);
|
||||
|
||||
// Send multiple rapid direction changes
|
||||
send_game_event(&mut world, GameCommand::MovePlayer(Direction::Up));
|
||||
world
|
||||
.run_system_once(player_control_system)
|
||||
.expect("System should run successfully");
|
||||
|
||||
send_game_event(&mut world, GameCommand::MovePlayer(Direction::Down));
|
||||
world
|
||||
.run_system_once(player_control_system)
|
||||
.expect("System should run successfully");
|
||||
|
||||
send_game_event(&mut world, GameCommand::MovePlayer(Direction::Left));
|
||||
world
|
||||
.run_system_once(player_control_system)
|
||||
.expect("System should run successfully");
|
||||
|
||||
// Only the last direction should be buffered
|
||||
let mut query = world.query::<&BufferedDirection>();
|
||||
let buffered_direction = query.single(&world).expect("Player should exist");
|
||||
|
||||
match *buffered_direction {
|
||||
BufferedDirection::Some { direction, .. } => {
|
||||
assert_eq!(direction, Direction::Left);
|
||||
}
|
||||
BufferedDirection::None => panic!("Expected buffered direction"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_player_state_persistence_across_systems() {
|
||||
let mut world = create_test_world();
|
||||
let _player = spawn_test_player(&mut world);
|
||||
|
||||
// Test that multiple commands can be processed - but need to handle events properly
|
||||
// Clear any existing events first
|
||||
world.resource_mut::<Events<GameEvent>>().clear();
|
||||
|
||||
// Toggle debug mode
|
||||
send_game_event(&mut world, GameCommand::ToggleDebug);
|
||||
world
|
||||
.run_system_once(player_control_system)
|
||||
.expect("System should run successfully");
|
||||
let debug_state_after_toggle = *world.resource::<DebugState>();
|
||||
|
||||
// Clear events and mute audio
|
||||
world.resource_mut::<Events<GameEvent>>().clear();
|
||||
send_game_event(&mut world, GameCommand::MuteAudio);
|
||||
world
|
||||
.run_system_once(player_control_system)
|
||||
.expect("System should run successfully");
|
||||
let audio_muted_after_toggle = world.resource::<AudioState>().muted;
|
||||
|
||||
// Clear events and move player
|
||||
world.resource_mut::<Events<GameEvent>>().clear();
|
||||
send_game_event(&mut world, GameCommand::MovePlayer(Direction::Down));
|
||||
world
|
||||
.run_system_once(player_control_system)
|
||||
.expect("System should run successfully");
|
||||
world
|
||||
.run_system_once(player_movement_system)
|
||||
.expect("System should run successfully");
|
||||
|
||||
// Check that all state changes persisted
|
||||
// Variables already captured above during individual tests
|
||||
let mut query = world.query::<&Position>();
|
||||
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!(audio_muted_after_toggle, "Audio should be muted");
|
||||
|
||||
// Player position depends on actual map connectivity
|
||||
match position {
|
||||
Position::Moving { .. } => {} // Good - player is moving
|
||||
Position::Stopped { .. } => {} // Also ok - might not have valid edge in that direction
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user