mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-10 20:07:53 -06:00
Compare commits
2 Commits
v0.57.0
...
b1b03b0e9c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1b03b0e9c | ||
|
|
a62ae8dfe7 |
@@ -49,6 +49,26 @@ pub const CANVAS_SIZE: UVec2 = UVec2::new(
|
|||||||
(BOARD_CELL_SIZE.y + BOARD_CELL_OFFSET.y) * CELL_SIZE,
|
(BOARD_CELL_SIZE.y + BOARD_CELL_OFFSET.y) * CELL_SIZE,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Collider size constants for different entity types
|
||||||
|
pub mod collider {
|
||||||
|
use super::CELL_SIZE;
|
||||||
|
|
||||||
|
/// Collider size for player and ghosts (1.375x cell size)
|
||||||
|
pub const PLAYER_GHOST_SIZE: f32 = CELL_SIZE as f32 * 1.375;
|
||||||
|
/// Collider size for pellets (0.4x cell size)
|
||||||
|
pub const PELLET_SIZE: f32 = CELL_SIZE as f32 * 0.4;
|
||||||
|
/// Collider size for power pellets/energizers (0.95x cell size)
|
||||||
|
pub const POWER_PELLET_SIZE: f32 = CELL_SIZE as f32 * 0.95;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// UI and rendering constants
|
||||||
|
pub mod ui {
|
||||||
|
/// Debug font size in points
|
||||||
|
pub const DEBUG_FONT_SIZE: u16 = 12;
|
||||||
|
/// Power pellet blink rate in seconds
|
||||||
|
pub const POWER_PELLET_BLINK_RATE: f32 = 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
/// Map tile types that define gameplay behavior and collision properties.
|
/// Map tile types that define gameplay behavior and collision properties.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum MapTile {
|
pub enum MapTile {
|
||||||
@@ -100,3 +120,17 @@ pub const RAW_BOARD: [&str; BOARD_CELL_SIZE.y as usize] = [
|
|||||||
"#..........................#",
|
"#..........................#",
|
||||||
"############################",
|
"############################",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/// Game initialization constants
|
||||||
|
pub mod startup {
|
||||||
|
/// Number of frames for the startup sequence (3 seconds at 60 FPS)
|
||||||
|
pub const STARTUP_FRAMES: u32 = 60 * 3;
|
||||||
|
/// Number of ticks per frame during startup
|
||||||
|
pub const STARTUP_TICKS_PER_FRAME: u32 = 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Game mechanics constants
|
||||||
|
pub mod mechanics {
|
||||||
|
/// Player movement speed multiplier
|
||||||
|
pub const PLAYER_SPEED: f32 = 1.15;
|
||||||
|
}
|
||||||
|
|||||||
19
src/game.rs
19
src/game.rs
@@ -110,7 +110,7 @@ impl Game {
|
|||||||
let static_font_data: &'static [u8] = Box::leak(font_data.to_vec().into_boxed_slice());
|
let static_font_data: &'static [u8] = Box::leak(font_data.to_vec().into_boxed_slice());
|
||||||
let font_asset = RWops::from_bytes(static_font_data).map_err(|_| GameError::Sdl("Failed to load font".to_string()))?;
|
let font_asset = RWops::from_bytes(static_font_data).map_err(|_| GameError::Sdl("Failed to load font".to_string()))?;
|
||||||
let debug_font = ttf_context
|
let debug_font = ttf_context
|
||||||
.load_font_from_rwops(font_asset, 12)
|
.load_font_from_rwops(font_asset, constants::ui::DEBUG_FONT_SIZE)
|
||||||
.map_err(|e| GameError::Sdl(e.to_string()))?;
|
.map_err(|e| GameError::Sdl(e.to_string()))?;
|
||||||
|
|
||||||
// Initialize audio system
|
// Initialize audio system
|
||||||
@@ -213,7 +213,7 @@ impl Game {
|
|||||||
node: map.start_positions.pacman,
|
node: map.start_positions.pacman,
|
||||||
},
|
},
|
||||||
velocity: Velocity {
|
velocity: Velocity {
|
||||||
speed: 1.15,
|
speed: constants::mechanics::PLAYER_SPEED,
|
||||||
direction: Direction::Left,
|
direction: Direction::Left,
|
||||||
},
|
},
|
||||||
movement_modifiers: MovementModifiers::default(),
|
movement_modifiers: MovementModifiers::default(),
|
||||||
@@ -226,7 +226,7 @@ impl Game {
|
|||||||
directional_animation: DirectionalAnimation::new(moving_tiles, stopped_tiles, 5),
|
directional_animation: DirectionalAnimation::new(moving_tiles, stopped_tiles, 5),
|
||||||
entity_type: EntityType::Player,
|
entity_type: EntityType::Player,
|
||||||
collider: Collider {
|
collider: Collider {
|
||||||
size: constants::CELL_SIZE as f32 * 1.375,
|
size: constants::collider::PLAYER_GHOST_SIZE,
|
||||||
},
|
},
|
||||||
pacman_collider: PacmanCollider,
|
pacman_collider: PacmanCollider,
|
||||||
};
|
};
|
||||||
@@ -249,7 +249,10 @@ impl Game {
|
|||||||
world.insert_resource(DebugState::default());
|
world.insert_resource(DebugState::default());
|
||||||
world.insert_resource(AudioState::default());
|
world.insert_resource(AudioState::default());
|
||||||
world.insert_resource(CursorPosition::default());
|
world.insert_resource(CursorPosition::default());
|
||||||
world.insert_resource(StartupSequence::new(60 * 3, 60));
|
world.insert_resource(StartupSequence::new(
|
||||||
|
constants::startup::STARTUP_FRAMES,
|
||||||
|
constants::startup::STARTUP_TICKS_PER_FRAME,
|
||||||
|
));
|
||||||
|
|
||||||
world.insert_non_send_resource(atlas);
|
world.insert_non_send_resource(atlas);
|
||||||
world.insert_non_send_resource(event_pump);
|
world.insert_non_send_resource(event_pump);
|
||||||
@@ -337,12 +340,12 @@ impl Game {
|
|||||||
.resource::<Map>()
|
.resource::<Map>()
|
||||||
.iter_nodes()
|
.iter_nodes()
|
||||||
.filter_map(|(id, tile)| match tile {
|
.filter_map(|(id, tile)| match tile {
|
||||||
MapTile::Pellet => Some((*id, EntityType::Pellet, pellet_sprite, constants::CELL_SIZE as f32 * 0.4)),
|
MapTile::Pellet => Some((*id, EntityType::Pellet, pellet_sprite, constants::collider::PELLET_SIZE)),
|
||||||
MapTile::PowerPellet => Some((
|
MapTile::PowerPellet => Some((
|
||||||
*id,
|
*id,
|
||||||
EntityType::PowerPellet,
|
EntityType::PowerPellet,
|
||||||
energizer_sprite,
|
energizer_sprite,
|
||||||
constants::CELL_SIZE as f32 * 0.95,
|
constants::collider::POWER_PELLET_SIZE,
|
||||||
)),
|
)),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
@@ -360,7 +363,7 @@ impl Game {
|
|||||||
|
|
||||||
// Make power pellets blink
|
// Make power pellets blink
|
||||||
if item_type == EntityType::PowerPellet {
|
if item_type == EntityType::PowerPellet {
|
||||||
item.insert((Frozen, Blinking::new(0.2)));
|
item.insert((Frozen, Blinking::new(constants::ui::POWER_PELLET_BLINK_RATE)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,7 +415,7 @@ impl Game {
|
|||||||
directional_animation: animations,
|
directional_animation: animations,
|
||||||
entity_type: EntityType::Ghost,
|
entity_type: EntityType::Ghost,
|
||||||
collider: Collider {
|
collider: Collider {
|
||||||
size: constants::CELL_SIZE as f32 * 1.375,
|
size: constants::collider::PLAYER_GHOST_SIZE,
|
||||||
},
|
},
|
||||||
ghost_collider: GhostCollider,
|
ghost_collider: GhostCollider,
|
||||||
ghost_state: GhostState::Normal,
|
ghost_state: GhostState::Normal,
|
||||||
|
|||||||
@@ -60,9 +60,11 @@ pub fn item_system(
|
|||||||
// Convert seconds to frames (assumes 60 FPS)
|
// Convert seconds to frames (assumes 60 FPS)
|
||||||
let total_ticks = 60 * 5; // 5 seconds total
|
let total_ticks = 60 * 5; // 5 seconds total
|
||||||
|
|
||||||
// Set all ghosts to frightened state
|
// Set all ghosts to frightened state, except those in Eyes state
|
||||||
for mut ghost_state in ghost_query.iter_mut() {
|
for mut ghost_state in ghost_query.iter_mut() {
|
||||||
*ghost_state = GhostState::new_frightened(total_ticks, FRIGHTENED_FLASH_START_TICKS);
|
if !matches!(*ghost_state, GhostState::Eyes) {
|
||||||
|
*ghost_state = GhostState::new_frightened(total_ticks, FRIGHTENED_FLASH_START_TICKS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ use pacman::{
|
|||||||
events::GameEvent,
|
events::GameEvent,
|
||||||
map::builder::Map,
|
map::builder::Map,
|
||||||
systems::{
|
systems::{
|
||||||
is_valid_item_collision, item_system, AudioEvent, AudioState, EntityType, ItemCollider, PacmanCollider, Position,
|
is_valid_item_collision, item_system, AudioEvent, AudioState, EntityType, Ghost, GhostCollider, GhostState, ItemCollider,
|
||||||
ScoreResource,
|
PacmanCollider, Position, ScoreResource,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -75,6 +75,18 @@ fn spawn_test_item(world: &mut World, item_type: EntityType) -> Entity {
|
|||||||
world.spawn((Position::Stopped { node: 1 }, item_type, ItemCollider)).id()
|
world.spawn((Position::Stopped { node: 1 }, item_type, ItemCollider)).id()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn spawn_test_ghost(world: &mut World, ghost_state: GhostState) -> Entity {
|
||||||
|
world
|
||||||
|
.spawn((
|
||||||
|
Position::Stopped { node: 2 },
|
||||||
|
Ghost::Blinky,
|
||||||
|
EntityType::Ghost,
|
||||||
|
GhostCollider,
|
||||||
|
ghost_state,
|
||||||
|
))
|
||||||
|
.id()
|
||||||
|
}
|
||||||
|
|
||||||
fn send_collision_event(world: &mut World, entity1: Entity, entity2: Entity) {
|
fn send_collision_event(world: &mut World, entity1: Entity, entity2: Entity) {
|
||||||
let mut events = world.resource_mut::<Events<GameEvent>>();
|
let mut events = world.resource_mut::<Events<GameEvent>>();
|
||||||
events.send(GameEvent::Collision(entity1, entity2));
|
events.send(GameEvent::Collision(entity1, entity2));
|
||||||
@@ -251,3 +263,40 @@ fn test_item_system_preserves_existing_score() {
|
|||||||
let score = world.resource::<ScoreResource>();
|
let score = world.resource::<ScoreResource>();
|
||||||
assert_eq!(score.0, 110);
|
assert_eq!(score.0, 110);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_power_pellet_does_not_affect_ghosts_in_eyes_state() {
|
||||||
|
let mut world = create_test_world();
|
||||||
|
let pacman = spawn_test_pacman(&mut world);
|
||||||
|
let power_pellet = spawn_test_item(&mut world, EntityType::PowerPellet);
|
||||||
|
|
||||||
|
// Spawn a ghost in Eyes state (returning to ghost house)
|
||||||
|
let eyes_ghost = spawn_test_ghost(&mut world, GhostState::Eyes);
|
||||||
|
|
||||||
|
// Spawn a ghost in Normal state
|
||||||
|
let normal_ghost = spawn_test_ghost(&mut world, GhostState::Normal);
|
||||||
|
|
||||||
|
send_collision_event(&mut world, pacman, power_pellet);
|
||||||
|
|
||||||
|
world.run_system_once(item_system).expect("System should run successfully");
|
||||||
|
|
||||||
|
// Check that the power pellet was collected and score updated
|
||||||
|
let score = world.resource::<ScoreResource>();
|
||||||
|
assert_eq!(score.0, 50);
|
||||||
|
|
||||||
|
// Check that the power pellet was despawned
|
||||||
|
let power_pellet_count = world
|
||||||
|
.query::<&EntityType>()
|
||||||
|
.iter(&world)
|
||||||
|
.filter(|&entity_type| matches!(entity_type, EntityType::PowerPellet))
|
||||||
|
.count();
|
||||||
|
assert_eq!(power_pellet_count, 0);
|
||||||
|
|
||||||
|
// Check that the Eyes ghost state was not changed
|
||||||
|
let eyes_ghost_state = world.entity(eyes_ghost).get::<GhostState>().unwrap();
|
||||||
|
assert!(matches!(*eyes_ghost_state, GhostState::Eyes));
|
||||||
|
|
||||||
|
// Check that the Normal ghost state was changed to Frightened
|
||||||
|
let normal_ghost_state = world.entity(normal_ghost).get::<GhostState>().unwrap();
|
||||||
|
assert!(matches!(*normal_ghost_state, GhostState::Frightened { .. }));
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user