mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-07 05:15:49 -06:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f1e1d4d42 | ||
|
|
9e029966dc | ||
|
|
968eb39b64 | ||
|
|
0759019c8b | ||
|
|
17188df729 |
7
Justfile
7
Justfile
@@ -3,7 +3,7 @@ set windows-shell := ["powershell.exe", "-NoLogo", "-Command"]
|
|||||||
|
|
||||||
# Regex to exclude files from coverage report, double escapes for Justfile + CLI
|
# Regex to exclude files from coverage report, double escapes for Justfile + CLI
|
||||||
# You can use src\\\\..., but the filename alone is acceptable too
|
# You can use src\\\\..., but the filename alone is acceptable too
|
||||||
coverage_exclude_pattern := "src\\\\app.rs|audio.rs|src\\\\error.rs|platform\\\\emscripten.rs"
|
coverage_exclude_pattern := "src\\\\app\\.rs|audio\\.rs|src\\\\error\\.rs|platform\\\\emscripten\\.rs|bin\\\\.+\\.rs|main\\.rs|platform\\\\desktop\\.rs|platform\\\\tracing_buffer\\.rs|platform\\\\buffered_writer\\.rs|systems\\\\debug\\.rs|systems\\\\profiling\\.rs"
|
||||||
|
|
||||||
binary_extension := if os() == "windows" { ".exe" } else { "" }
|
binary_extension := if os() == "windows" { ".exe" } else { "" }
|
||||||
|
|
||||||
@@ -40,5 +40,6 @@ samply:
|
|||||||
samply record ./target/profile/pacman{{ binary_extension }}
|
samply record ./target/profile/pacman{{ binary_extension }}
|
||||||
|
|
||||||
# Build the project for Emscripten
|
# Build the project for Emscripten
|
||||||
web:
|
web *args:
|
||||||
bun run web.build.ts; caddy file-server --root dist
|
bun run web.build.ts {{args}};
|
||||||
|
caddy file-server --root dist
|
||||||
|
|||||||
@@ -49,6 +49,13 @@ 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,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
pub const LARGE_SCALE: f32 = 2.6;
|
||||||
|
|
||||||
|
pub const LARGE_CANVAS_SIZE: UVec2 = UVec2::new(
|
||||||
|
(((BOARD_CELL_SIZE.x + BOARD_CELL_OFFSET.x) * CELL_SIZE) as f32 * LARGE_SCALE) as u32,
|
||||||
|
(((BOARD_CELL_SIZE.y + BOARD_CELL_OFFSET.y) * CELL_SIZE) as f32 * LARGE_SCALE) as u32,
|
||||||
|
);
|
||||||
|
|
||||||
/// Collider size constants for different entity types
|
/// Collider size constants for different entity types
|
||||||
pub mod collider {
|
pub mod collider {
|
||||||
use super::CELL_SIZE;
|
use super::CELL_SIZE;
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ impl Game {
|
|||||||
EventType::TextInput,
|
EventType::TextInput,
|
||||||
EventType::TextEditing,
|
EventType::TextEditing,
|
||||||
EventType::Display,
|
EventType::Display,
|
||||||
EventType::Window,
|
// EventType::Window,
|
||||||
EventType::MouseWheel,
|
EventType::MouseWheel,
|
||||||
// EventType::MouseMotion,
|
// EventType::MouseMotion,
|
||||||
// EventType::MouseButtonDown, // Enable for desktop touch testing
|
// EventType::MouseButtonDown, // Enable for desktop touch testing
|
||||||
@@ -157,9 +157,9 @@ impl Game {
|
|||||||
map_texture.set_scale_mode(ScaleMode::Nearest);
|
map_texture.set_scale_mode(ScaleMode::Nearest);
|
||||||
|
|
||||||
// Create debug texture at output resolution for crisp debug rendering
|
// Create debug texture at output resolution for crisp debug rendering
|
||||||
let output_size = canvas.output_size().unwrap();
|
let output_size = constants::LARGE_CANVAS_SIZE;
|
||||||
let mut debug_texture = texture_creator
|
let mut debug_texture = texture_creator
|
||||||
.create_texture_target(Some(sdl2::pixels::PixelFormatEnum::ARGB8888), output_size.0, output_size.1)
|
.create_texture_target(Some(sdl2::pixels::PixelFormatEnum::ARGB8888), output_size.x, output_size.y)
|
||||||
.map_err(|e| GameError::Sdl(e.to_string()))?;
|
.map_err(|e| GameError::Sdl(e.to_string()))?;
|
||||||
|
|
||||||
// Debug texture is copied over the backbuffer, it requires transparency abilities
|
// Debug texture is copied over the backbuffer, it requires transparency abilities
|
||||||
|
|||||||
18
src/main.rs
18
src/main.rs
@@ -2,7 +2,7 @@
|
|||||||
#![windows_subsystem = "windows"]
|
#![windows_subsystem = "windows"]
|
||||||
|
|
||||||
use crate::{app::App, constants::LOOP_TIME};
|
use crate::{app::App, constants::LOOP_TIME};
|
||||||
use tracing::{debug, info, warn};
|
use tracing::info;
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
mod asset;
|
mod asset;
|
||||||
@@ -22,19 +22,9 @@ mod texture;
|
|||||||
/// This function initializes SDL, the window, the game state, and then enters
|
/// This function initializes SDL, the window, the game state, and then enters
|
||||||
/// the main game loop.
|
/// the main game loop.
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
if platform::requires_console() {
|
// On Windows, this connects output streams to the console dynamically
|
||||||
// Setup buffered tracing subscriber that will buffer logs until console is ready
|
// On Emscripten, this connects the subscriber to the browser console
|
||||||
let switchable_writer = platform::tracing_buffer::setup_switchable_subscriber();
|
platform::init_console().expect("Could not initialize console");
|
||||||
|
|
||||||
// Initialize platform-specific console
|
|
||||||
platform::init_console().expect("Could not initialize console");
|
|
||||||
|
|
||||||
// Now that console is initialized, flush buffered logs and switch to direct output
|
|
||||||
debug!("Switching to direct logging mode and flushing buffer...");
|
|
||||||
if let Err(error) = switchable_writer.switch_to_direct_mode() {
|
|
||||||
warn!("Failed to flush buffered logs to console: {error:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut app = App::new().expect("Could not create app");
|
let mut app = App::new().expect("Could not create app");
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,13 @@ pub fn sleep(duration: Duration, focused: bool) {
|
|||||||
pub fn init_console() -> Result<(), PlatformError> {
|
pub fn init_console() -> Result<(), PlatformError> {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
|
use crate::platform::tracing_buffer::setup_switchable_subscriber;
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
use windows::Win32::System::Console::GetConsoleWindow;
|
use windows::Win32::System::Console::GetConsoleWindow;
|
||||||
|
|
||||||
|
// Setup buffered tracing subscriber that will buffer logs until console is ready
|
||||||
|
let switchable_writer = setup_switchable_subscriber();
|
||||||
|
|
||||||
// Check if we already have a console window
|
// Check if we already have a console window
|
||||||
if unsafe { !GetConsoleWindow().0.is_null() } {
|
if unsafe { !GetConsoleWindow().0.is_null() } {
|
||||||
debug!("Already have a console window");
|
debug!("Already have a console window");
|
||||||
@@ -40,15 +44,19 @@ pub fn init_console() -> Result<(), PlatformError> {
|
|||||||
attach_to_parent_console()?;
|
attach_to_parent_console()?;
|
||||||
info!("Successfully attached to parent console");
|
info!("Successfully attached to parent console");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now that console is initialized, flush buffered logs and switch to direct output
|
||||||
|
debug!("Switching to direct logging mode and flushing buffer...");
|
||||||
|
if let Err(error) = switchable_writer.switch_to_direct_mode() {
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
|
warn!("Failed to flush buffered logs to console: {error:?}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn requires_console() -> bool {
|
|
||||||
cfg!(windows)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_asset_bytes(asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> {
|
pub fn get_asset_bytes(asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> {
|
||||||
match asset {
|
match asset {
|
||||||
Asset::Wav1 => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/waka/1.ogg"))),
|
Asset::Wav1 => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/waka/1.ogg"))),
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
//! Emscripten platform implementation.
|
//! Emscripten platform implementation.
|
||||||
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use crate::asset::Asset;
|
use crate::asset::Asset;
|
||||||
use crate::error::{AssetError, PlatformError};
|
use crate::error::{AssetError, PlatformError};
|
||||||
use rand::{rngs::SmallRng, SeedableRng};
|
use rand::{rngs::SmallRng, SeedableRng};
|
||||||
|
use sdl2::rwops::RWops;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::ffi::CString;
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
// Emscripten FFI functions
|
// Emscripten FFI functions
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn emscripten_get_now() -> f64;
|
|
||||||
fn emscripten_sleep(ms: u32);
|
fn emscripten_sleep(ms: u32);
|
||||||
fn emscripten_get_element_css_size(target: *const u8, width: *mut f64, height: *mut f64) -> i32;
|
fn emscripten_get_element_css_size(target: *const u8, width: *mut f64, height: *mut f64) -> i32;
|
||||||
|
// Standard C functions that Emscripten redirects to console
|
||||||
|
fn printf(format: *const u8, ...) -> i32;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sleep(duration: Duration, _focused: bool) {
|
pub fn sleep(duration: Duration, _focused: bool) {
|
||||||
@@ -22,11 +25,44 @@ pub fn sleep(duration: Duration, _focused: bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_console() -> Result<(), PlatformError> {
|
pub fn init_console() -> Result<(), PlatformError> {
|
||||||
Ok(()) // No-op for Emscripten
|
use tracing_subscriber::{fmt, layer::SubscriberExt, EnvFilter};
|
||||||
|
|
||||||
|
// Set up a custom tracing subscriber that writes directly to emscripten console
|
||||||
|
let subscriber = tracing_subscriber::registry()
|
||||||
|
.with(
|
||||||
|
fmt::layer()
|
||||||
|
.with_writer(|| EmscriptenConsoleWriter)
|
||||||
|
.with_ansi(false)
|
||||||
|
.without_time()
|
||||||
|
.with_target(false),
|
||||||
|
)
|
||||||
|
.with(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug")));
|
||||||
|
|
||||||
|
tracing::subscriber::set_global_default(subscriber)
|
||||||
|
.map_err(|e| PlatformError::ConsoleInit(format!("Failed to set tracing subscriber: {}", e)))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn requires_console() -> bool {
|
/// A writer that outputs to the browser console via printf (redirected by emscripten)
|
||||||
false
|
struct EmscriptenConsoleWriter;
|
||||||
|
|
||||||
|
impl Write for EmscriptenConsoleWriter {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
if let Ok(s) = std::str::from_utf8(buf) {
|
||||||
|
if let Ok(cstr) = CString::new(s.trim_end_matches('\n')) {
|
||||||
|
let format_str = CString::new("%s\n").unwrap();
|
||||||
|
unsafe {
|
||||||
|
printf(format_str.as_ptr().cast(), cstr.as_ptr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(buf.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -44,18 +80,13 @@ pub fn get_canvas_size() -> Option<(u32, u32)> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_asset_bytes(asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> {
|
pub fn get_asset_bytes(asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> {
|
||||||
use sdl2::rwops::RWops;
|
|
||||||
use std::io::Read;
|
|
||||||
|
|
||||||
let path = format!("assets/game/{}", asset.path());
|
let path = format!("assets/game/{}", asset.path());
|
||||||
let mut rwops = RWops::from_file(&path, "rb").map_err(|_| AssetError::NotFound(asset.path().to_string()))?;
|
let mut rwops = RWops::from_file(&path, "rb").map_err(|_| AssetError::NotFound(asset.path().to_string()))?;
|
||||||
|
|
||||||
let len = rwops.len().ok_or_else(|| AssetError::NotFound(asset.path().to_string()))?;
|
let len = rwops.len().ok_or_else(|| AssetError::NotFound(asset.path().to_string()))?;
|
||||||
|
|
||||||
let mut buf = vec![0u8; len];
|
let mut buf = vec![0u8; len];
|
||||||
rwops
|
rwops.read_exact(&mut buf).map_err(|e| AssetError::Io(io::Error::other(e)))?;
|
||||||
.read_exact(&mut buf)
|
|
||||||
.map_err(|e| AssetError::Io(std::io::Error::other(e)))?;
|
|
||||||
|
|
||||||
Ok(Cow::Owned(buf))
|
Ok(Cow::Owned(buf))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
//! Platform abstraction layer for cross-platform functionality.
|
//! Platform abstraction layer for cross-platform functionality.
|
||||||
pub mod buffered_writer;
|
|
||||||
pub mod tracing_buffer;
|
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "emscripten"))]
|
||||||
|
pub mod buffered_writer;
|
||||||
#[cfg(not(target_os = "emscripten"))]
|
#[cfg(not(target_os = "emscripten"))]
|
||||||
mod desktop;
|
mod desktop;
|
||||||
#[cfg(not(target_os = "emscripten"))]
|
#[cfg(not(target_os = "emscripten"))]
|
||||||
|
pub mod tracing_buffer;
|
||||||
|
#[cfg(not(target_os = "emscripten"))]
|
||||||
pub use desktop::*;
|
pub use desktop::*;
|
||||||
|
|
||||||
#[cfg(target_os = "emscripten")]
|
#[cfg(target_os = "emscripten")]
|
||||||
|
|||||||
@@ -142,8 +142,6 @@ pub fn ghost_collision_system(
|
|||||||
events.write(AudioEvent::PlayEat);
|
events.write(AudioEvent::PlayEat);
|
||||||
} else {
|
} else {
|
||||||
// Pac-Man dies (this would need a death system)
|
// Pac-Man dies (this would need a death system)
|
||||||
// For now, just log it
|
|
||||||
tracing::warn!("Pac-Man collided with ghost while not frightened!");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Debug rendering system
|
//! Debug rendering system
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use crate::constants::{BOARD_PIXEL_OFFSET, CANVAS_SIZE};
|
use crate::constants::{self, BOARD_PIXEL_OFFSET};
|
||||||
use crate::map::builder::Map;
|
use crate::map::builder::Map;
|
||||||
use crate::systems::{Collider, CursorPosition, NodeId, Position, SystemTimings};
|
use crate::systems::{Collider, CursorPosition, NodeId, Position, SystemTimings};
|
||||||
use crate::texture::ttf::{TtfAtlas, TtfRenderer};
|
use crate::texture::ttf::{TtfAtlas, TtfRenderer};
|
||||||
@@ -215,9 +215,7 @@ pub fn debug_render_system(
|
|||||||
if !debug_state.enabled {
|
if !debug_state.enabled {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let output = UVec2::from(canvas.output_size().unwrap()).as_vec2();
|
let scale = constants::LARGE_SCALE as f32;
|
||||||
let logical = CANVAS_SIZE.as_vec2();
|
|
||||||
let scale = (output / logical).min_element();
|
|
||||||
|
|
||||||
// Create debug text renderer
|
// Create debug text renderer
|
||||||
let text_renderer = TtfRenderer::new(1.0);
|
let text_renderer = TtfRenderer::new(1.0);
|
||||||
|
|||||||
@@ -293,6 +293,9 @@ pub fn input_system(
|
|||||||
simple_key_events.push(SimpleKeyEvent::KeyUp(key));
|
simple_key_events.push(SimpleKeyEvent::KeyUp(key));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Event::RenderTargetsReset { .. } | Event::Window { .. } => {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
tracing::warn!("Unhandled event, consider disabling: {:?}", event);
|
tracing::warn!("Unhandled event, consider disabling: {:?}", event);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
// use glam::U16Vec2;
|
|
||||||
// use pacman::error::{AnimatedTextureError, GameError, TextureError};
|
|
||||||
// use pacman::texture::sprite::AtlasTile;
|
|
||||||
// use sdl2::pixels::Color;
|
|
||||||
// use smallvec::smallvec;
|
|
||||||
|
|
||||||
// fn mock_atlas_tile(id: u32) -> AtlasTile {
|
|
||||||
// AtlasTile {
|
|
||||||
// pos: U16Vec2::new(0, 0),
|
|
||||||
// size: U16Vec2::new(16, 16),
|
|
||||||
// color: Some(Color::RGB(id as u8, 0, 0)),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_animated_texture_creation_errors() {
|
|
||||||
// let tiles = smallvec![mock_atlas_tile(1), mock_atlas_tile(2)];
|
|
||||||
|
|
||||||
// assert!(matches!(
|
|
||||||
// AnimatedTexture::new(tiles.clone(), 0).unwrap_err(),
|
|
||||||
// GameError::Texture(TextureError::Animated(AnimatedTextureError::InvalidFrameDuration(0)))
|
|
||||||
// ));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_animated_texture_advancement() {
|
|
||||||
// let tiles = smallvec![mock_atlas_tile(1), mock_atlas_tile(2), mock_atlas_tile(3)];
|
|
||||||
// let mut texture = AnimatedTexture::new(tiles, 10).unwrap();
|
|
||||||
|
|
||||||
// assert_eq!(texture.current_frame(), 0);
|
|
||||||
|
|
||||||
// texture.tick(25);
|
|
||||||
// assert_eq!(texture.current_frame(), 2);
|
|
||||||
// assert_eq!(texture.time_bank(), 5);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_animated_texture_wrap_around() {
|
|
||||||
// let tiles = smallvec![mock_atlas_tile(1), mock_atlas_tile(2)];
|
|
||||||
// let mut texture = AnimatedTexture::new(tiles, 10).unwrap();
|
|
||||||
|
|
||||||
// texture.tick(10);
|
|
||||||
// assert_eq!(texture.current_frame(), 1);
|
|
||||||
|
|
||||||
// texture.tick(10);
|
|
||||||
// assert_eq!(texture.current_frame(), 0);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_animated_texture_single_frame() {
|
|
||||||
// let tiles = smallvec![mock_atlas_tile(1)];
|
|
||||||
// let mut texture = AnimatedTexture::new(tiles, 10).unwrap();
|
|
||||||
|
|
||||||
// texture.tick(10);
|
|
||||||
// assert_eq!(texture.current_frame(), 0);
|
|
||||||
// assert_eq!(texture.current_tile().color.unwrap().r, 1);
|
|
||||||
// }
|
|
||||||
@@ -1,19 +1,10 @@
|
|||||||
use glam::U16Vec2;
|
|
||||||
use pacman::texture::blinking::BlinkingTexture;
|
use pacman::texture::blinking::BlinkingTexture;
|
||||||
use pacman::texture::sprite::AtlasTile;
|
|
||||||
use sdl2::pixels::Color;
|
|
||||||
|
|
||||||
fn mock_atlas_tile(id: u32) -> AtlasTile {
|
mod common;
|
||||||
AtlasTile {
|
|
||||||
pos: U16Vec2::new(0, 0),
|
|
||||||
size: U16Vec2::new(16, 16),
|
|
||||||
color: Some(Color::RGB(id as u8, 0, 0)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_blinking_texture() {
|
fn test_blinking_texture() {
|
||||||
let tile = mock_atlas_tile(1);
|
let tile = common::mock_atlas_tile(1);
|
||||||
let mut texture = BlinkingTexture::new(tile, 0.5);
|
let mut texture = BlinkingTexture::new(tile, 0.5);
|
||||||
|
|
||||||
assert!(texture.is_on());
|
assert!(texture.is_on());
|
||||||
@@ -30,7 +21,7 @@ fn test_blinking_texture() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_blinking_texture_partial_duration() {
|
fn test_blinking_texture_partial_duration() {
|
||||||
let tile = mock_atlas_tile(1);
|
let tile = common::mock_atlas_tile(1);
|
||||||
let mut texture = BlinkingTexture::new(tile, 0.5);
|
let mut texture = BlinkingTexture::new(tile, 0.5);
|
||||||
|
|
||||||
texture.tick(0.625);
|
texture.tick(0.625);
|
||||||
@@ -40,7 +31,7 @@ fn test_blinking_texture_partial_duration() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_blinking_texture_negative_time() {
|
fn test_blinking_texture_negative_time() {
|
||||||
let tile = mock_atlas_tile(1);
|
let tile = common::mock_atlas_tile(1);
|
||||||
let mut texture = BlinkingTexture::new(tile, 0.5);
|
let mut texture = BlinkingTexture::new(tile, 0.5);
|
||||||
|
|
||||||
texture.tick(-0.1);
|
texture.tick(-0.1);
|
||||||
|
|||||||
@@ -1,73 +1,7 @@
|
|||||||
use bevy_ecs::{entity::Entity, event::Events, system::RunSystemOnce, world::World};
|
use bevy_ecs::system::RunSystemOnce;
|
||||||
|
use pacman::systems::{check_collision, collision_system, Collider, EntityType, GhostState, Position};
|
||||||
|
|
||||||
use pacman::{
|
mod common;
|
||||||
error::GameError,
|
|
||||||
events::GameEvent,
|
|
||||||
map::builder::Map,
|
|
||||||
systems::{
|
|
||||||
check_collision, collision_system, Collider, EntityType, Ghost, GhostCollider, ItemCollider, NodeId, PacmanCollider,
|
|
||||||
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: node as NodeId },
|
|
||||||
Collider { size: 12.0 },
|
|
||||||
GhostCollider,
|
|
||||||
Ghost::Blinky,
|
|
||||||
EntityType::Ghost,
|
|
||||||
))
|
|
||||||
.id()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collider_collision_detection() {
|
fn test_collider_collision_detection() {
|
||||||
@@ -81,7 +15,7 @@ fn test_collider_collision_detection() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_check_collision_helper() {
|
fn test_check_collision_helper() {
|
||||||
let map = create_test_map();
|
let map = common::create_test_map();
|
||||||
let pos1 = Position::Stopped { node: 0 };
|
let pos1 = Position::Stopped { node: 0 };
|
||||||
let pos2 = Position::Stopped { node: 0 }; // Same position
|
let pos2 = Position::Stopped { node: 0 }; // Same position
|
||||||
let collider1 = Collider { size: 10.0 };
|
let collider1 = Collider { size: 10.0 };
|
||||||
@@ -101,9 +35,9 @@ fn test_check_collision_helper() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collision_system_pacman_item() {
|
fn test_collision_system_pacman_item() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let _pacman = spawn_test_pacman(&mut world);
|
let _pacman = common::spawn_test_pacman(&mut world, 0);
|
||||||
let _item = spawn_test_item(&mut world);
|
let _item = common::spawn_test_item(&mut world, 0, EntityType::Pellet);
|
||||||
|
|
||||||
// Run collision system - should not panic
|
// Run collision system - should not panic
|
||||||
world
|
world
|
||||||
@@ -113,9 +47,9 @@ fn test_collision_system_pacman_item() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collision_system_pacman_ghost() {
|
fn test_collision_system_pacman_ghost() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let _pacman = spawn_test_pacman(&mut world);
|
let _pacman = common::spawn_test_pacman(&mut world, 0);
|
||||||
let _ghost = spawn_test_ghost(&mut world);
|
let _ghost = common::spawn_test_ghost(&mut world, 0, GhostState::Normal);
|
||||||
|
|
||||||
// Run collision system - should not panic
|
// Run collision system - should not panic
|
||||||
world
|
world
|
||||||
@@ -125,9 +59,9 @@ fn test_collision_system_pacman_ghost() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collision_system_no_collision() {
|
fn test_collision_system_no_collision() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let _pacman = spawn_test_pacman(&mut world);
|
let _pacman = common::spawn_test_pacman(&mut world, 0);
|
||||||
let _ghost = spawn_test_ghost_at_node(&mut world, 1); // Different node
|
let _ghost = common::spawn_test_ghost(&mut world, 1, GhostState::Normal); // Different node
|
||||||
|
|
||||||
// Run collision system - should not panic
|
// Run collision system - should not panic
|
||||||
world
|
world
|
||||||
@@ -137,10 +71,10 @@ fn test_collision_system_no_collision() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collision_system_multiple_entities() {
|
fn test_collision_system_multiple_entities() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let _pacman = spawn_test_pacman(&mut world);
|
let _pacman = common::spawn_test_pacman(&mut world, 0);
|
||||||
let _item = spawn_test_item(&mut world);
|
let _item = common::spawn_test_item(&mut world, 0, EntityType::Pellet);
|
||||||
let _ghost = spawn_test_ghost(&mut world);
|
let _ghost = common::spawn_test_ghost(&mut world, 0, GhostState::Normal);
|
||||||
|
|
||||||
// Run collision system - should not panic
|
// Run collision system - should not panic
|
||||||
world
|
world
|
||||||
|
|||||||
@@ -1,12 +1,26 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use bevy_ecs::{entity::Entity, event::Events, world::World};
|
||||||
|
use glam::{U16Vec2, Vec2};
|
||||||
use pacman::{
|
use pacman::{
|
||||||
asset::{get_asset_bytes, Asset},
|
asset::{get_asset_bytes, Asset},
|
||||||
|
constants::RAW_BOARD,
|
||||||
|
events::GameEvent,
|
||||||
game::ATLAS_FRAMES,
|
game::ATLAS_FRAMES,
|
||||||
texture::sprite::{AtlasMapper, SpriteAtlas},
|
map::{
|
||||||
|
builder::Map,
|
||||||
|
direction::Direction,
|
||||||
|
graph::{Graph, Node},
|
||||||
|
},
|
||||||
|
systems::{
|
||||||
|
AudioEvent, AudioState, BufferedDirection, Collider, DebugState, DeltaTime, EntityType, Ghost, GhostCollider, GhostState,
|
||||||
|
GlobalState, ItemCollider, MovementModifiers, PacmanCollider, PlayerControlled, Position, ScoreResource, Velocity,
|
||||||
|
},
|
||||||
|
texture::sprite::{AtlasMapper, AtlasTile, SpriteAtlas},
|
||||||
};
|
};
|
||||||
use sdl2::{
|
use sdl2::{
|
||||||
image::LoadTexture,
|
image::LoadTexture,
|
||||||
|
pixels::Color,
|
||||||
render::{Canvas, TextureCreator},
|
render::{Canvas, TextureCreator},
|
||||||
video::{Window, WindowContext},
|
video::{Window, WindowContext},
|
||||||
Sdl,
|
Sdl,
|
||||||
@@ -38,3 +52,122 @@ pub fn create_atlas(canvas: &mut sdl2::render::Canvas<sdl2::video::Window>) -> S
|
|||||||
|
|
||||||
SpriteAtlas::new(texture, atlas_mapper)
|
SpriteAtlas::new(texture, atlas_mapper)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a simple test graph with 3 connected nodes for testing
|
||||||
|
pub fn create_test_graph() -> Graph {
|
||||||
|
let mut graph = Graph::new();
|
||||||
|
|
||||||
|
let node0 = graph.add_node(Node {
|
||||||
|
position: Vec2::new(0.0, 0.0),
|
||||||
|
});
|
||||||
|
let node1 = graph.add_node(Node {
|
||||||
|
position: Vec2::new(16.0, 0.0),
|
||||||
|
});
|
||||||
|
let node2 = graph.add_node(Node {
|
||||||
|
position: Vec2::new(0.0, 16.0),
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.connect(node0, node1, false, None, Direction::Right).unwrap();
|
||||||
|
graph.connect(node0, node2, false, None, Direction::Down).unwrap();
|
||||||
|
|
||||||
|
graph
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a basic test world with required resources for ECS systems
|
||||||
|
pub fn create_test_world() -> World {
|
||||||
|
let mut world = World::new();
|
||||||
|
|
||||||
|
// Add required resources
|
||||||
|
world.insert_resource(Events::<GameEvent>::default());
|
||||||
|
world.insert_resource(Events::<pacman::error::GameError>::default());
|
||||||
|
world.insert_resource(Events::<AudioEvent>::default());
|
||||||
|
world.insert_resource(ScoreResource(0));
|
||||||
|
world.insert_resource(AudioState::default());
|
||||||
|
world.insert_resource(GlobalState { exit: false });
|
||||||
|
world.insert_resource(DebugState::default());
|
||||||
|
world.insert_resource(DeltaTime(1.0 / 60.0)); // 60 FPS
|
||||||
|
world.insert_resource(create_test_map());
|
||||||
|
|
||||||
|
world
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a test map using the default RAW_BOARD
|
||||||
|
pub fn create_test_map() -> Map {
|
||||||
|
Map::new(RAW_BOARD).expect("Failed to create test map")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawns a test Pac-Man entity at the specified node
|
||||||
|
pub fn spawn_test_pacman(world: &mut World, node: usize) -> Entity {
|
||||||
|
world
|
||||||
|
.spawn((
|
||||||
|
Position::Stopped { node: node as u16 },
|
||||||
|
Collider { size: 10.0 },
|
||||||
|
PacmanCollider,
|
||||||
|
EntityType::Player,
|
||||||
|
))
|
||||||
|
.id()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawns a controllable test player entity
|
||||||
|
pub fn spawn_test_player(world: &mut World, node: usize) -> Entity {
|
||||||
|
world
|
||||||
|
.spawn((
|
||||||
|
PlayerControlled,
|
||||||
|
Position::Stopped { node: node as u16 },
|
||||||
|
Velocity {
|
||||||
|
speed: 1.0,
|
||||||
|
direction: Direction::Right,
|
||||||
|
},
|
||||||
|
BufferedDirection::None,
|
||||||
|
EntityType::Player,
|
||||||
|
MovementModifiers::default(),
|
||||||
|
))
|
||||||
|
.id()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawns a test item entity at the specified node
|
||||||
|
pub fn spawn_test_item(world: &mut World, node: usize, item_type: EntityType) -> Entity {
|
||||||
|
world
|
||||||
|
.spawn((
|
||||||
|
Position::Stopped { node: node as u16 },
|
||||||
|
Collider { size: 8.0 },
|
||||||
|
ItemCollider,
|
||||||
|
item_type,
|
||||||
|
))
|
||||||
|
.id()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawns a test ghost entity at the specified node
|
||||||
|
pub fn spawn_test_ghost(world: &mut World, node: usize, ghost_state: GhostState) -> Entity {
|
||||||
|
world
|
||||||
|
.spawn((
|
||||||
|
Position::Stopped { node: node as u16 },
|
||||||
|
Collider { size: 12.0 },
|
||||||
|
GhostCollider,
|
||||||
|
Ghost::Blinky,
|
||||||
|
EntityType::Ghost,
|
||||||
|
ghost_state,
|
||||||
|
))
|
||||||
|
.id()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends a game event to the world
|
||||||
|
pub fn send_game_event(world: &mut World, event: GameEvent) {
|
||||||
|
let mut events = world.resource_mut::<Events<GameEvent>>();
|
||||||
|
events.send(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a mock atlas tile for testing
|
||||||
|
pub fn mock_atlas_tile(id: u32) -> AtlasTile {
|
||||||
|
AtlasTile {
|
||||||
|
pos: U16Vec2::new(0, 0),
|
||||||
|
size: U16Vec2::new(16, 16),
|
||||||
|
color: Some(Color::RGB(id as u8, 0, 0)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
use pacman::events::{GameCommand, GameEvent};
|
|
||||||
use pacman::map::direction::Direction;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_game_command_to_game_event_conversion_all_variants() {
|
|
||||||
let commands = vec![
|
|
||||||
GameCommand::Exit,
|
|
||||||
GameCommand::MovePlayer(Direction::Up),
|
|
||||||
GameCommand::ToggleDebug,
|
|
||||||
GameCommand::MuteAudio,
|
|
||||||
GameCommand::ResetLevel,
|
|
||||||
GameCommand::TogglePause,
|
|
||||||
];
|
|
||||||
|
|
||||||
for command in commands {
|
|
||||||
let event: GameEvent = command.into();
|
|
||||||
assert_eq!(event, GameEvent::Command(command));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,7 @@
|
|||||||
use pacman::map::direction::Direction;
|
use pacman::map::direction::Direction;
|
||||||
use pacman::map::graph::{Graph, Node, TraversalFlags};
|
use pacman::map::graph::{Graph, Node, TraversalFlags};
|
||||||
|
|
||||||
fn create_test_graph() -> Graph {
|
mod common;
|
||||||
let mut graph = Graph::new();
|
|
||||||
let node1 = graph.add_node(Node {
|
|
||||||
position: glam::Vec2::new(0.0, 0.0),
|
|
||||||
});
|
|
||||||
let node2 = graph.add_node(Node {
|
|
||||||
position: glam::Vec2::new(16.0, 0.0),
|
|
||||||
});
|
|
||||||
let node3 = graph.add_node(Node {
|
|
||||||
position: glam::Vec2::new(0.0, 16.0),
|
|
||||||
});
|
|
||||||
|
|
||||||
graph.connect(node1, node2, false, None, Direction::Right).unwrap();
|
|
||||||
graph.connect(node1, node3, false, None, Direction::Down).unwrap();
|
|
||||||
|
|
||||||
graph
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_graph_basic_operations() {
|
fn test_graph_basic_operations() {
|
||||||
@@ -124,14 +108,14 @@ fn should_error_on_negative_edge_distance() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_error_on_duplicate_edge_without_replace() {
|
fn should_error_on_duplicate_edge_without_replace() {
|
||||||
let mut graph = create_test_graph();
|
let mut graph = common::create_test_graph();
|
||||||
let result = graph.add_edge(0, 1, false, None, Direction::Right, TraversalFlags::ALL);
|
let result = graph.add_edge(0, 1, false, None, Direction::Right, TraversalFlags::ALL);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_allow_replacing_an_edge() {
|
fn should_allow_replacing_an_edge() {
|
||||||
let mut graph = create_test_graph();
|
let mut graph = common::create_test_graph();
|
||||||
let result = graph.add_edge(0, 1, true, Some(42.0), Direction::Right, TraversalFlags::ALL);
|
let result = graph.add_edge(0, 1, true, Some(42.0), Direction::Right, TraversalFlags::ALL);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
||||||
@@ -141,7 +125,7 @@ fn should_allow_replacing_an_edge() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_find_edge_between_nodes() {
|
fn should_find_edge_between_nodes() {
|
||||||
let graph = create_test_graph();
|
let graph = common::create_test_graph();
|
||||||
let edge = graph.find_edge(0, 1);
|
let edge = graph.find_edge(0, 1);
|
||||||
assert!(edge.is_some());
|
assert!(edge.is_some());
|
||||||
assert_eq!(edge.unwrap().target, 1);
|
assert_eq!(edge.unwrap().target, 1);
|
||||||
|
|||||||
26
tests/hud.rs
26
tests/hud.rs
@@ -1,26 +0,0 @@
|
|||||||
use bevy_ecs::{event::Events, world::World};
|
|
||||||
|
|
||||||
use pacman::{error::GameError, systems::components::ScoreResource};
|
|
||||||
|
|
||||||
fn create_test_world() -> World {
|
|
||||||
let mut world = World::new();
|
|
||||||
|
|
||||||
// Add required resources
|
|
||||||
world.insert_resource(Events::<GameError>::default());
|
|
||||||
world.insert_resource(ScoreResource(1230)); // Test score
|
|
||||||
|
|
||||||
world
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_hud_render_system_runs_without_error() {
|
|
||||||
let world = create_test_world();
|
|
||||||
|
|
||||||
// The HUD render system requires SDL2 resources that aren't available in tests,
|
|
||||||
// but we can at least verify it doesn't panic when called
|
|
||||||
// In a real test environment, we'd need to mock the SDL2 canvas and atlas
|
|
||||||
|
|
||||||
// For now, just verify the score resource is accessible
|
|
||||||
let score = world.resource::<ScoreResource>();
|
|
||||||
assert_eq!(score.0, 1230);
|
|
||||||
}
|
|
||||||
130
tests/item.rs
130
tests/item.rs
@@ -1,13 +1,7 @@
|
|||||||
use bevy_ecs::{entity::Entity, event::Events, system::RunSystemOnce, world::World};
|
use bevy_ecs::{entity::Entity, system::RunSystemOnce};
|
||||||
|
use pacman::systems::{is_valid_item_collision, item_system, EntityType, GhostState, Position, ScoreResource};
|
||||||
|
|
||||||
use pacman::{
|
mod common;
|
||||||
events::GameEvent,
|
|
||||||
map::builder::Map,
|
|
||||||
systems::{
|
|
||||||
is_valid_item_collision, item_system, AudioEvent, AudioState, EntityType, Ghost, GhostCollider, GhostState, ItemCollider,
|
|
||||||
PacmanCollider, Position, ScoreResource,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_calculate_score_for_item() {
|
fn test_calculate_score_for_item() {
|
||||||
@@ -44,62 +38,14 @@ fn test_is_valid_item_collision() {
|
|||||||
assert!(!is_valid_item_collision(EntityType::Player, EntityType::Player));
|
assert!(!is_valid_item_collision(EntityType::Player, EntityType::Player));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_test_world() -> World {
|
|
||||||
let mut world = World::new();
|
|
||||||
|
|
||||||
// Add required resources
|
|
||||||
world.insert_resource(ScoreResource(0));
|
|
||||||
world.insert_resource(AudioState::default());
|
|
||||||
world.insert_resource(Events::<GameEvent>::default());
|
|
||||||
world.insert_resource(Events::<AudioEvent>::default());
|
|
||||||
world.insert_resource(Events::<pacman::error::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 }, EntityType::Player, PacmanCollider))
|
|
||||||
.id()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn spawn_test_item(world: &mut World, item_type: EntityType) -> Entity {
|
|
||||||
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) {
|
|
||||||
let mut events = world.resource_mut::<Events<GameEvent>>();
|
|
||||||
events.send(GameEvent::Collision(entity1, entity2));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_item_system_pellet_collection() {
|
fn test_item_system_pellet_collection() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let pacman = spawn_test_pacman(&mut world);
|
let pacman = common::spawn_test_pacman(&mut world, 0);
|
||||||
let pellet = spawn_test_item(&mut world, EntityType::Pellet);
|
let pellet = common::spawn_test_item(&mut world, 1, EntityType::Pellet);
|
||||||
|
|
||||||
// Send collision event
|
// Send collision event
|
||||||
send_collision_event(&mut world, pacman, pellet);
|
common::send_collision_event(&mut world, pacman, pellet);
|
||||||
|
|
||||||
// Run the item system
|
// Run the item system
|
||||||
world.run_system_once(item_system).expect("System should run successfully");
|
world.run_system_once(item_system).expect("System should run successfully");
|
||||||
@@ -119,11 +65,11 @@ fn test_item_system_pellet_collection() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_item_system_power_pellet_collection() {
|
fn test_item_system_power_pellet_collection() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let pacman = spawn_test_pacman(&mut world);
|
let pacman = common::spawn_test_pacman(&mut world, 0);
|
||||||
let power_pellet = spawn_test_item(&mut world, EntityType::PowerPellet);
|
let power_pellet = common::spawn_test_item(&mut world, 1, EntityType::PowerPellet);
|
||||||
|
|
||||||
send_collision_event(&mut world, pacman, power_pellet);
|
common::send_collision_event(&mut world, pacman, power_pellet);
|
||||||
|
|
||||||
world.run_system_once(item_system).expect("System should run successfully");
|
world.run_system_once(item_system).expect("System should run successfully");
|
||||||
|
|
||||||
@@ -142,16 +88,16 @@ fn test_item_system_power_pellet_collection() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_item_system_multiple_collections() {
|
fn test_item_system_multiple_collections() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let pacman = spawn_test_pacman(&mut world);
|
let pacman = common::spawn_test_pacman(&mut world, 0);
|
||||||
let pellet1 = spawn_test_item(&mut world, EntityType::Pellet);
|
let pellet1 = common::spawn_test_item(&mut world, 1, EntityType::Pellet);
|
||||||
let pellet2 = spawn_test_item(&mut world, EntityType::Pellet);
|
let pellet2 = common::spawn_test_item(&mut world, 2, EntityType::Pellet);
|
||||||
let power_pellet = spawn_test_item(&mut world, EntityType::PowerPellet);
|
let power_pellet = common::spawn_test_item(&mut world, 3, EntityType::PowerPellet);
|
||||||
|
|
||||||
// Send multiple collision events
|
// Send multiple collision events
|
||||||
send_collision_event(&mut world, pacman, pellet1);
|
common::send_collision_event(&mut world, pacman, pellet1);
|
||||||
send_collision_event(&mut world, pacman, pellet2);
|
common::send_collision_event(&mut world, pacman, pellet2);
|
||||||
send_collision_event(&mut world, pacman, power_pellet);
|
common::send_collision_event(&mut world, pacman, power_pellet);
|
||||||
|
|
||||||
world.run_system_once(item_system).expect("System should run successfully");
|
world.run_system_once(item_system).expect("System should run successfully");
|
||||||
|
|
||||||
@@ -176,8 +122,8 @@ fn test_item_system_multiple_collections() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_item_system_ignores_non_item_collisions() {
|
fn test_item_system_ignores_non_item_collisions() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let pacman = spawn_test_pacman(&mut world);
|
let pacman = common::spawn_test_pacman(&mut world, 0);
|
||||||
|
|
||||||
// Create a ghost entity (not an item)
|
// Create a ghost entity (not an item)
|
||||||
let ghost = world.spawn((Position::Stopped { node: 2 }, EntityType::Ghost)).id();
|
let ghost = world.spawn((Position::Stopped { node: 2 }, EntityType::Ghost)).id();
|
||||||
@@ -186,7 +132,7 @@ fn test_item_system_ignores_non_item_collisions() {
|
|||||||
let initial_score = world.resource::<ScoreResource>().0;
|
let initial_score = world.resource::<ScoreResource>().0;
|
||||||
|
|
||||||
// Send collision event between pacman and ghost
|
// Send collision event between pacman and ghost
|
||||||
send_collision_event(&mut world, pacman, ghost);
|
common::send_collision_event(&mut world, pacman, ghost);
|
||||||
|
|
||||||
world.run_system_once(item_system).expect("System should run successfully");
|
world.run_system_once(item_system).expect("System should run successfully");
|
||||||
|
|
||||||
@@ -205,9 +151,9 @@ fn test_item_system_ignores_non_item_collisions() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_item_system_no_collision_events() {
|
fn test_item_system_no_collision_events() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let _pacman = spawn_test_pacman(&mut world);
|
let _pacman = common::spawn_test_pacman(&mut world, 0);
|
||||||
let _pellet = spawn_test_item(&mut world, EntityType::Pellet);
|
let _pellet = common::spawn_test_item(&mut world, 1, EntityType::Pellet);
|
||||||
|
|
||||||
let initial_score = world.resource::<ScoreResource>().0;
|
let initial_score = world.resource::<ScoreResource>().0;
|
||||||
|
|
||||||
@@ -227,13 +173,13 @@ fn test_item_system_no_collision_events() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_item_system_collision_with_missing_entity() {
|
fn test_item_system_collision_with_missing_entity() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let pacman = spawn_test_pacman(&mut world);
|
let pacman = common::spawn_test_pacman(&mut world, 0);
|
||||||
|
|
||||||
// Create a fake entity ID that doesn't exist
|
// Create a fake entity ID that doesn't exist
|
||||||
let fake_entity = Entity::from_raw(999);
|
let fake_entity = Entity::from_raw(999);
|
||||||
|
|
||||||
send_collision_event(&mut world, pacman, fake_entity);
|
common::send_collision_event(&mut world, pacman, fake_entity);
|
||||||
|
|
||||||
// System should handle gracefully and not crash
|
// System should handle gracefully and not crash
|
||||||
world
|
world
|
||||||
@@ -247,15 +193,15 @@ fn test_item_system_collision_with_missing_entity() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_item_system_preserves_existing_score() {
|
fn test_item_system_preserves_existing_score() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
|
|
||||||
// Set initial score
|
// Set initial score
|
||||||
world.insert_resource(ScoreResource(100));
|
world.insert_resource(ScoreResource(100));
|
||||||
|
|
||||||
let pacman = spawn_test_pacman(&mut world);
|
let pacman = common::spawn_test_pacman(&mut world, 0);
|
||||||
let pellet = spawn_test_item(&mut world, EntityType::Pellet);
|
let pellet = common::spawn_test_item(&mut world, 1, EntityType::Pellet);
|
||||||
|
|
||||||
send_collision_event(&mut world, pacman, pellet);
|
common::send_collision_event(&mut world, pacman, pellet);
|
||||||
|
|
||||||
world.run_system_once(item_system).expect("System should run successfully");
|
world.run_system_once(item_system).expect("System should run successfully");
|
||||||
|
|
||||||
@@ -266,17 +212,17 @@ fn test_item_system_preserves_existing_score() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_power_pellet_does_not_affect_ghosts_in_eyes_state() {
|
fn test_power_pellet_does_not_affect_ghosts_in_eyes_state() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let pacman = spawn_test_pacman(&mut world);
|
let pacman = common::spawn_test_pacman(&mut world, 0);
|
||||||
let power_pellet = spawn_test_item(&mut world, EntityType::PowerPellet);
|
let power_pellet = common::spawn_test_item(&mut world, 1, EntityType::PowerPellet);
|
||||||
|
|
||||||
// Spawn a ghost in Eyes state (returning to ghost house)
|
// Spawn a ghost in Eyes state (returning to ghost house)
|
||||||
let eyes_ghost = spawn_test_ghost(&mut world, GhostState::Eyes);
|
let eyes_ghost = common::spawn_test_ghost(&mut world, 2, GhostState::Eyes);
|
||||||
|
|
||||||
// Spawn a ghost in Normal state
|
// Spawn a ghost in Normal state
|
||||||
let normal_ghost = spawn_test_ghost(&mut world, GhostState::Normal);
|
let normal_ghost = common::spawn_test_ghost(&mut world, 3, GhostState::Normal);
|
||||||
|
|
||||||
send_collision_event(&mut world, pacman, power_pellet);
|
common::send_collision_event(&mut world, pacman, power_pellet);
|
||||||
|
|
||||||
world.run_system_once(item_system).expect("System should run successfully");
|
world.run_system_once(item_system).expect("System should run successfully");
|
||||||
|
|
||||||
|
|||||||
@@ -34,61 +34,3 @@ fn test_map_node_positions() {
|
|||||||
assert_eq!(node.position, expected_pos);
|
assert_eq!(node.position, expected_pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_generate_items() {
|
|
||||||
// use pacman::texture::sprite::{AtlasMapper, MapperFrame, SpriteAtlas};
|
|
||||||
// use std::collections::HashMap;
|
|
||||||
|
|
||||||
// let map = Map::new(RAW_BOARD).unwrap();
|
|
||||||
|
|
||||||
// // Create a minimal atlas for testing
|
|
||||||
// let mut frames = HashMap::new();
|
|
||||||
// frames.insert(
|
|
||||||
// "maze/pellet.png".to_string(),
|
|
||||||
// MapperFrame {
|
|
||||||
// x: 0,
|
|
||||||
// y: 0,
|
|
||||||
// width: 8,
|
|
||||||
// height: 8,
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
// frames.insert(
|
|
||||||
// "maze/energizer.png".to_string(),
|
|
||||||
// MapperFrame {
|
|
||||||
// x: 8,
|
|
||||||
// y: 0,
|
|
||||||
// width: 8,
|
|
||||||
// height: 8,
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
|
|
||||||
// let mapper = AtlasMapper { frames };
|
|
||||||
// let texture = unsafe { std::mem::transmute::<usize, Texture<'static>>(0usize) };
|
|
||||||
// let atlas = SpriteAtlas::new(texture, mapper);
|
|
||||||
|
|
||||||
// let items = map.generate_items(&atlas).unwrap();
|
|
||||||
|
|
||||||
// // Verify we have items
|
|
||||||
// assert!(!items.is_empty());
|
|
||||||
|
|
||||||
// // Count different types
|
|
||||||
// let pellet_count = items
|
|
||||||
// .iter()
|
|
||||||
// .filter(|item| matches!(item.item_type, pacman::entity::item::ItemType::Pellet))
|
|
||||||
// .count();
|
|
||||||
// let energizer_count = items
|
|
||||||
// .iter()
|
|
||||||
// .filter(|item| matches!(item.item_type, pacman::entity::item::ItemType::Energizer))
|
|
||||||
// .count();
|
|
||||||
|
|
||||||
// // Should have both types
|
|
||||||
// assert_eq!(pellet_count, 240);
|
|
||||||
// assert_eq!(energizer_count, 4);
|
|
||||||
|
|
||||||
// // All items should be uncollected initially
|
|
||||||
// assert!(items.iter().all(|item| !item.is_collected()));
|
|
||||||
|
|
||||||
// // All items should have valid node indices
|
|
||||||
// assert!(items.iter().all(|item| item.node_index < map.graph.node_count()));
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -1,28 +1,8 @@
|
|||||||
use glam::Vec2;
|
use glam::Vec2;
|
||||||
use pacman::map::direction::Direction;
|
use pacman::map::direction::Direction;
|
||||||
use pacman::map::graph::{Graph, Node};
|
|
||||||
use pacman::systems::movement::{BufferedDirection, Position, Velocity};
|
use pacman::systems::movement::{BufferedDirection, Position, Velocity};
|
||||||
|
|
||||||
fn create_test_graph() -> Graph {
|
mod common;
|
||||||
let mut graph = Graph::new();
|
|
||||||
|
|
||||||
// Add a few test nodes
|
|
||||||
let node0 = graph.add_node(Node {
|
|
||||||
position: Vec2::new(0.0, 0.0),
|
|
||||||
});
|
|
||||||
let node1 = graph.add_node(Node {
|
|
||||||
position: Vec2::new(16.0, 0.0),
|
|
||||||
});
|
|
||||||
let node2 = graph.add_node(Node {
|
|
||||||
position: Vec2::new(0.0, 16.0),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Connect them
|
|
||||||
graph.connect(node0, node1, false, None, Direction::Right).unwrap();
|
|
||||||
graph.connect(node0, node2, false, None, Direction::Down).unwrap();
|
|
||||||
|
|
||||||
graph
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_position_is_at_node() {
|
fn test_position_is_at_node() {
|
||||||
@@ -127,7 +107,7 @@ fn test_position_tick_overshoot_with_overflow() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_position_get_pixel_position_stopped() {
|
fn test_position_get_pixel_position_stopped() {
|
||||||
let graph = create_test_graph();
|
let graph = common::create_test_graph();
|
||||||
let pos = Position::Stopped { node: 0 };
|
let pos = Position::Stopped { node: 0 };
|
||||||
|
|
||||||
let pixel_pos = pos.get_pixel_position(&graph).unwrap();
|
let pixel_pos = pos.get_pixel_position(&graph).unwrap();
|
||||||
@@ -141,7 +121,7 @@ fn test_position_get_pixel_position_stopped() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_position_get_pixel_position_moving() {
|
fn test_position_get_pixel_position_moving() {
|
||||||
let graph = create_test_graph();
|
let graph = common::create_test_graph();
|
||||||
let pos = Position::Moving {
|
let pos = Position::Moving {
|
||||||
from: 0,
|
from: 0,
|
||||||
to: 1,
|
to: 1,
|
||||||
|
|||||||
142
tests/player.rs
142
tests/player.rs
@@ -1,63 +1,17 @@
|
|||||||
use bevy_ecs::{entity::Entity, event::Events, system::RunSystemOnce, world::World};
|
use bevy_ecs::{event::Events, system::RunSystemOnce};
|
||||||
|
|
||||||
use pacman::{
|
use pacman::{
|
||||||
events::{GameCommand, GameEvent},
|
events::{GameCommand, GameEvent},
|
||||||
map::{
|
map::{
|
||||||
builder::Map,
|
|
||||||
direction::Direction,
|
direction::Direction,
|
||||||
graph::{Edge, TraversalFlags},
|
graph::{Edge, TraversalFlags},
|
||||||
},
|
},
|
||||||
systems::{
|
systems::{
|
||||||
can_traverse, player_control_system, player_movement_system, AudioState, BufferedDirection, DebugState, DeltaTime,
|
can_traverse, player_control_system, player_movement_system, AudioState, BufferedDirection, DebugState, DeltaTime,
|
||||||
EntityType, GlobalState, MovementModifiers, PlayerControlled, Position, Velocity,
|
EntityType, GlobalState, Position, Velocity,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Test helper functions for ECS setup
|
mod common;
|
||||||
fn create_test_world() -> World {
|
|
||||||
let mut world = World::new();
|
|
||||||
|
|
||||||
// Add resources
|
|
||||||
world.insert_resource(GlobalState { exit: false });
|
|
||||||
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());
|
|
||||||
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,
|
|
||||||
MovementModifiers::default(),
|
|
||||||
))
|
|
||||||
.id()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_game_event(world: &mut World, command: GameCommand) {
|
|
||||||
let mut events = world.resource_mut::<Events<GameEvent>>();
|
|
||||||
events.send(GameEvent::Command(command));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_can_traverse_player_on_all_edges() {
|
fn test_can_traverse_player_on_all_edges() {
|
||||||
@@ -155,17 +109,13 @@ fn test_entity_type_traversal_flags() {
|
|||||||
assert_eq!(EntityType::PowerPellet.traversal_flags(), TraversalFlags::empty());
|
assert_eq!(EntityType::PowerPellet.traversal_flags(), TraversalFlags::empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// ECS System Tests
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_player_control_system_move_command() {
|
fn test_player_control_system_move_command() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let _player = spawn_test_player(&mut world);
|
let _player = common::spawn_test_player(&mut world, 0);
|
||||||
|
|
||||||
// Send move command
|
// Send move command
|
||||||
send_game_event(&mut world, GameCommand::MovePlayer(Direction::Up));
|
common::send_game_event(&mut world, GameEvent::Command(GameCommand::MovePlayer(Direction::Up)));
|
||||||
|
|
||||||
// Run the system
|
// Run the system
|
||||||
world
|
world
|
||||||
@@ -190,11 +140,11 @@ fn test_player_control_system_move_command() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_player_control_system_exit_command() {
|
fn test_player_control_system_exit_command() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let _player = spawn_test_player(&mut world);
|
let _player = common::spawn_test_player(&mut world, 0);
|
||||||
|
|
||||||
// Send exit command
|
// Send exit command
|
||||||
send_game_event(&mut world, GameCommand::Exit);
|
common::send_game_event(&mut world, GameEvent::Command(GameCommand::Exit));
|
||||||
|
|
||||||
// Run the system
|
// Run the system
|
||||||
world
|
world
|
||||||
@@ -208,11 +158,11 @@ fn test_player_control_system_exit_command() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_player_control_system_toggle_debug() {
|
fn test_player_control_system_toggle_debug() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let _player = spawn_test_player(&mut world);
|
let _player = common::spawn_test_player(&mut world, 0);
|
||||||
|
|
||||||
// Send toggle debug command
|
// Send toggle debug command
|
||||||
send_game_event(&mut world, GameCommand::ToggleDebug);
|
common::send_game_event(&mut world, GameEvent::Command(GameCommand::ToggleDebug));
|
||||||
|
|
||||||
// Run the system
|
// Run the system
|
||||||
world
|
world
|
||||||
@@ -226,11 +176,11 @@ fn test_player_control_system_toggle_debug() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_player_control_system_mute_audio() {
|
fn test_player_control_system_mute_audio() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let _player = spawn_test_player(&mut world);
|
let _player = common::spawn_test_player(&mut world, 0);
|
||||||
|
|
||||||
// Send mute audio command
|
// Send mute audio command
|
||||||
send_game_event(&mut world, GameCommand::MuteAudio);
|
common::send_game_event(&mut world, GameEvent::Command(GameCommand::MuteAudio));
|
||||||
|
|
||||||
// Run the system
|
// Run the system
|
||||||
world
|
world
|
||||||
@@ -243,7 +193,7 @@ fn test_player_control_system_mute_audio() {
|
|||||||
|
|
||||||
// Send mute audio command again to unmute - need fresh events
|
// Send mute audio command again to unmute - need fresh events
|
||||||
world.resource_mut::<Events<GameEvent>>().clear(); // Clear previous events
|
world.resource_mut::<Events<GameEvent>>().clear(); // Clear previous events
|
||||||
send_game_event(&mut world, GameCommand::MuteAudio);
|
common::send_game_event(&mut world, GameEvent::Command(GameCommand::MuteAudio));
|
||||||
world
|
world
|
||||||
.run_system_once(player_control_system)
|
.run_system_once(player_control_system)
|
||||||
.expect("System should run successfully");
|
.expect("System should run successfully");
|
||||||
@@ -255,10 +205,10 @@ fn test_player_control_system_mute_audio() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_player_control_system_no_player_entity() {
|
fn test_player_control_system_no_player_entity() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
// Don't spawn a player entity
|
// Don't spawn a player entity
|
||||||
|
|
||||||
send_game_event(&mut world, GameCommand::MovePlayer(Direction::Up));
|
common::send_game_event(&mut world, GameEvent::Command(GameCommand::MovePlayer(Direction::Up)));
|
||||||
|
|
||||||
// Run the system - should write an error
|
// Run the system - should write an error
|
||||||
world
|
world
|
||||||
@@ -272,8 +222,8 @@ fn test_player_control_system_no_player_entity() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_player_movement_system_buffered_direction_expires() {
|
fn test_player_movement_system_buffered_direction_expires() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let player = spawn_test_player(&mut world);
|
let player = common::spawn_test_player(&mut world, 0);
|
||||||
|
|
||||||
// Set a buffered direction with short time
|
// Set a buffered direction with short time
|
||||||
world.entity_mut(player).insert(BufferedDirection::Some {
|
world.entity_mut(player).insert(BufferedDirection::Some {
|
||||||
@@ -305,8 +255,8 @@ fn test_player_movement_system_buffered_direction_expires() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_player_movement_system_start_moving_from_stopped() {
|
fn test_player_movement_system_start_moving_from_stopped() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let _player = spawn_test_player(&mut world);
|
let _player = common::spawn_test_player(&mut world, 0);
|
||||||
|
|
||||||
// Player starts at node 0, facing right (towards node 1)
|
// Player starts at node 0, facing right (towards node 1)
|
||||||
// Should start moving when system runs
|
// Should start moving when system runs
|
||||||
@@ -330,8 +280,8 @@ fn test_player_movement_system_start_moving_from_stopped() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_player_movement_system_buffered_direction_change() {
|
fn test_player_movement_system_buffered_direction_change() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let player = spawn_test_player(&mut world);
|
let player = common::spawn_test_player(&mut world, 0);
|
||||||
|
|
||||||
// Set a buffered direction to go down (towards node 2)
|
// Set a buffered direction to go down (towards node 2)
|
||||||
world.entity_mut(player).insert(BufferedDirection::Some {
|
world.entity_mut(player).insert(BufferedDirection::Some {
|
||||||
@@ -361,8 +311,8 @@ fn test_player_movement_system_buffered_direction_change() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_player_movement_system_no_valid_edge() {
|
fn test_player_movement_system_no_valid_edge() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let player = spawn_test_player(&mut world);
|
let player = common::spawn_test_player(&mut world, 0);
|
||||||
|
|
||||||
// Set velocity to direction with no edge
|
// Set velocity to direction with no edge
|
||||||
world.entity_mut(player).insert(Velocity {
|
world.entity_mut(player).insert(Velocity {
|
||||||
@@ -386,8 +336,8 @@ fn test_player_movement_system_no_valid_edge() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_player_movement_system_continue_moving() {
|
fn test_player_movement_system_continue_moving() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let player = spawn_test_player(&mut world);
|
let player = common::spawn_test_player(&mut world, 0);
|
||||||
|
|
||||||
// Set player to already be moving
|
// Set player to already be moving
|
||||||
world.entity_mut(player).insert(Position::Moving {
|
world.entity_mut(player).insert(Position::Moving {
|
||||||
@@ -414,17 +364,13 @@ fn test_player_movement_system_continue_moving() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Integration Tests
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_full_player_input_to_movement_flow() {
|
fn test_full_player_input_to_movement_flow() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let _player = spawn_test_player(&mut world);
|
let _player = common::spawn_test_player(&mut world, 0);
|
||||||
|
|
||||||
// Send move command
|
// Send move command
|
||||||
send_game_event(&mut world, GameCommand::MovePlayer(Direction::Down));
|
common::send_game_event(&mut world, GameEvent::Command(GameCommand::MovePlayer(Direction::Down)));
|
||||||
|
|
||||||
// Run control system to process input
|
// Run control system to process input
|
||||||
world
|
world
|
||||||
@@ -454,11 +400,11 @@ fn test_full_player_input_to_movement_flow() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_buffered_direction_timing() {
|
fn test_buffered_direction_timing() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let _player = spawn_test_player(&mut world);
|
let _player = common::spawn_test_player(&mut world, 0);
|
||||||
|
|
||||||
// Send move command
|
// Send move command
|
||||||
send_game_event(&mut world, GameCommand::MovePlayer(Direction::Up));
|
common::send_game_event(&mut world, GameEvent::Command(GameCommand::MovePlayer(Direction::Up)));
|
||||||
world
|
world
|
||||||
.run_system_once(player_control_system)
|
.run_system_once(player_control_system)
|
||||||
.expect("System should run successfully");
|
.expect("System should run successfully");
|
||||||
@@ -493,21 +439,21 @@ fn test_buffered_direction_timing() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_multiple_rapid_direction_changes() {
|
fn test_multiple_rapid_direction_changes() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let _player = spawn_test_player(&mut world);
|
let _player = common::spawn_test_player(&mut world, 0);
|
||||||
|
|
||||||
// Send multiple rapid direction changes
|
// Send multiple rapid direction changes
|
||||||
send_game_event(&mut world, GameCommand::MovePlayer(Direction::Up));
|
common::send_game_event(&mut world, GameEvent::Command(GameCommand::MovePlayer(Direction::Up)));
|
||||||
world
|
world
|
||||||
.run_system_once(player_control_system)
|
.run_system_once(player_control_system)
|
||||||
.expect("System should run successfully");
|
.expect("System should run successfully");
|
||||||
|
|
||||||
send_game_event(&mut world, GameCommand::MovePlayer(Direction::Down));
|
common::send_game_event(&mut world, GameEvent::Command(GameCommand::MovePlayer(Direction::Down)));
|
||||||
world
|
world
|
||||||
.run_system_once(player_control_system)
|
.run_system_once(player_control_system)
|
||||||
.expect("System should run successfully");
|
.expect("System should run successfully");
|
||||||
|
|
||||||
send_game_event(&mut world, GameCommand::MovePlayer(Direction::Left));
|
common::send_game_event(&mut world, GameEvent::Command(GameCommand::MovePlayer(Direction::Left)));
|
||||||
world
|
world
|
||||||
.run_system_once(player_control_system)
|
.run_system_once(player_control_system)
|
||||||
.expect("System should run successfully");
|
.expect("System should run successfully");
|
||||||
@@ -526,15 +472,15 @@ fn test_multiple_rapid_direction_changes() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_player_state_persistence_across_systems() {
|
fn test_player_state_persistence_across_systems() {
|
||||||
let mut world = create_test_world();
|
let mut world = common::create_test_world();
|
||||||
let _player = spawn_test_player(&mut world);
|
let _player = common::spawn_test_player(&mut world, 0);
|
||||||
|
|
||||||
// Test that multiple commands can be processed - but need to handle events properly
|
// Test that multiple commands can be processed - but need to handle events properly
|
||||||
// Clear any existing events first
|
// Clear any existing events first
|
||||||
world.resource_mut::<Events<GameEvent>>().clear();
|
world.resource_mut::<Events<GameEvent>>().clear();
|
||||||
|
|
||||||
// Toggle debug mode
|
// Toggle debug mode
|
||||||
send_game_event(&mut world, GameCommand::ToggleDebug);
|
common::send_game_event(&mut world, GameEvent::Command(GameCommand::ToggleDebug));
|
||||||
world
|
world
|
||||||
.run_system_once(player_control_system)
|
.run_system_once(player_control_system)
|
||||||
.expect("System should run successfully");
|
.expect("System should run successfully");
|
||||||
@@ -542,7 +488,7 @@ fn test_player_state_persistence_across_systems() {
|
|||||||
|
|
||||||
// Clear events and mute audio
|
// Clear events and mute audio
|
||||||
world.resource_mut::<Events<GameEvent>>().clear();
|
world.resource_mut::<Events<GameEvent>>().clear();
|
||||||
send_game_event(&mut world, GameCommand::MuteAudio);
|
common::send_game_event(&mut world, GameEvent::Command(GameCommand::MuteAudio));
|
||||||
world
|
world
|
||||||
.run_system_once(player_control_system)
|
.run_system_once(player_control_system)
|
||||||
.expect("System should run successfully");
|
.expect("System should run successfully");
|
||||||
@@ -550,7 +496,7 @@ fn test_player_state_persistence_across_systems() {
|
|||||||
|
|
||||||
// Clear events and move player
|
// Clear events and move player
|
||||||
world.resource_mut::<Events<GameEvent>>().clear();
|
world.resource_mut::<Events<GameEvent>>().clear();
|
||||||
send_game_event(&mut world, GameCommand::MovePlayer(Direction::Down));
|
common::send_game_event(&mut world, GameEvent::Command(GameCommand::MovePlayer(Direction::Down)));
|
||||||
world
|
world
|
||||||
.run_system_once(player_control_system)
|
.run_system_once(player_control_system)
|
||||||
.expect("System should run successfully");
|
.expect("System should run successfully");
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
use glam::U16Vec2;
|
use glam::U16Vec2;
|
||||||
use pacman::texture::sprite::{AtlasMapper, AtlasTile, MapperFrame, SpriteAtlas};
|
use pacman::texture::sprite::{AtlasMapper, AtlasTile, MapperFrame};
|
||||||
use sdl2::pixels::Color;
|
use sdl2::pixels::Color;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
fn mock_texture() -> sdl2::render::Texture {
|
mod common;
|
||||||
unsafe { std::mem::transmute(0usize) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sprite_atlas_basic() {
|
fn test_atlas_mapper_frame_lookup() {
|
||||||
let mut frames = HashMap::new();
|
let mut frames = HashMap::new();
|
||||||
frames.insert(
|
frames.insert(
|
||||||
"test".to_string(),
|
"test".to_string(),
|
||||||
@@ -19,19 +17,17 @@ fn test_sprite_atlas_basic() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let mapper = AtlasMapper { frames };
|
let mapper = AtlasMapper { frames };
|
||||||
let texture = mock_texture();
|
|
||||||
let atlas = SpriteAtlas::new(texture, mapper);
|
|
||||||
|
|
||||||
let tile = atlas.get_tile("test");
|
// Test direct frame lookup
|
||||||
assert!(tile.is_some());
|
let frame = mapper.frames.get("test");
|
||||||
let tile = tile.unwrap();
|
assert!(frame.is_some());
|
||||||
assert_eq!(tile.pos, glam::U16Vec2::new(10, 20));
|
let frame = frame.unwrap();
|
||||||
assert_eq!(tile.size, glam::U16Vec2::new(32, 64));
|
assert_eq!(frame.pos, U16Vec2::new(10, 20));
|
||||||
assert_eq!(tile.color, None);
|
assert_eq!(frame.size, U16Vec2::new(32, 64));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sprite_atlas_multiple_tiles() {
|
fn test_atlas_mapper_multiple_frames() {
|
||||||
let mut frames = HashMap::new();
|
let mut frames = HashMap::new();
|
||||||
frames.insert(
|
frames.insert(
|
||||||
"tile1".to_string(),
|
"tile1".to_string(),
|
||||||
@@ -49,27 +45,12 @@ fn test_sprite_atlas_multiple_tiles() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let mapper = AtlasMapper { frames };
|
let mapper = AtlasMapper { frames };
|
||||||
let texture = mock_texture();
|
|
||||||
let atlas = SpriteAtlas::new(texture, mapper);
|
|
||||||
|
|
||||||
assert_eq!(atlas.tiles_count(), 2);
|
assert_eq!(mapper.frames.len(), 2);
|
||||||
assert!(atlas.has_tile("tile1"));
|
assert!(mapper.frames.contains_key("tile1"));
|
||||||
assert!(atlas.has_tile("tile2"));
|
assert!(mapper.frames.contains_key("tile2"));
|
||||||
assert!(!atlas.has_tile("tile3"));
|
assert!(!mapper.frames.contains_key("tile3"));
|
||||||
assert!(atlas.get_tile("nonexistent").is_none());
|
assert!(!mapper.frames.contains_key("nonexistent"));
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sprite_atlas_color() {
|
|
||||||
let mapper = AtlasMapper { frames: HashMap::new() };
|
|
||||||
let texture = mock_texture();
|
|
||||||
let mut atlas = SpriteAtlas::new(texture, mapper);
|
|
||||||
|
|
||||||
assert_eq!(atlas.default_color(), None);
|
|
||||||
|
|
||||||
let color = Color::RGB(255, 0, 0);
|
|
||||||
atlas.set_color(color);
|
|
||||||
assert_eq!(atlas.default_color(), Some(color));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use pacman::texture::{sprite::SpriteAtlas, text::TextTexture};
|
use pacman::texture::{sprite::SpriteAtlas, text::TextTexture};
|
||||||
|
|
||||||
use crate::common::create_atlas;
|
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
|
use common::create_atlas;
|
||||||
|
|
||||||
/// Helper function to get all characters that should be in the atlas
|
/// Helper function to get all characters that should be in the atlas
|
||||||
fn get_all_chars() -> String {
|
fn get_all_chars() -> String {
|
||||||
let mut chars = Vec::new();
|
let mut chars = Vec::new();
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
use pacman::platform::tracing_buffer::SwitchableWriter;
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_switchable_writer_buffering() {
|
|
||||||
let mut writer = SwitchableWriter::default();
|
|
||||||
|
|
||||||
// Write some data while in buffered mode
|
|
||||||
writer.write_all(b"Hello, ").unwrap();
|
|
||||||
writer.write_all(b"world!").unwrap();
|
|
||||||
writer.write_all(b"This is buffered content.\n").unwrap();
|
|
||||||
|
|
||||||
// Switch to direct mode (this should flush to stdout and show buffer size)
|
|
||||||
// In a real test we can't easily capture stdout, so we'll just verify it doesn't panic
|
|
||||||
writer.switch_to_direct_mode().unwrap();
|
|
||||||
|
|
||||||
// Write more data in direct mode
|
|
||||||
writer.write_all(b"Direct output after flush\n").unwrap();
|
|
||||||
}
|
|
||||||
15
web.build.ts
15
web.build.ts
@@ -501,7 +501,6 @@ async function activateEmsdk(
|
|||||||
|
|
||||||
return { vars };
|
return { vars };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
// Print the OS detected
|
// Print the OS detected
|
||||||
logger.debug(
|
logger.debug(
|
||||||
@@ -515,7 +514,19 @@ async function main() {
|
|||||||
.exhaustive()
|
.exhaustive()
|
||||||
);
|
);
|
||||||
|
|
||||||
const release = process.env.RELEASE !== "0";
|
// Parse command line args for build mode
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
let release = true; // Default to release mode
|
||||||
|
|
||||||
|
for (let i = 0; i < args.length; i++) {
|
||||||
|
const arg = args[i];
|
||||||
|
if (arg === "-d" || arg === "--debug") {
|
||||||
|
release = false;
|
||||||
|
} else if (arg === "-r" || arg === "--release") {
|
||||||
|
release = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const emsdkDir = resolve("./emsdk");
|
const emsdkDir = resolve("./emsdk");
|
||||||
|
|
||||||
// Activate the Emscripten SDK (returns null if already activated)
|
// Activate the Emscripten SDK (returns null if already activated)
|
||||||
|
|||||||
Reference in New Issue
Block a user