mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-06 13:15:47 -06:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c79ba0d824 | ||
|
|
b1b03b0e9c |
@@ -25,8 +25,8 @@ pub struct App {
|
||||
impl App {
|
||||
/// Initializes SDL subsystems, creates the game window, and sets up the game state.
|
||||
///
|
||||
/// Performs comprehensive initialization including video/audio subsystems, platform-specific
|
||||
/// console setup, window creation with proper scaling, and canvas configuration. All SDL
|
||||
/// Performs comprehensive initialization including video/audio subsystems,
|
||||
/// window creation with proper scaling, and canvas configuration. All SDL
|
||||
/// resources are leaked to maintain 'static lifetimes required by the game architecture.
|
||||
///
|
||||
/// # Errors
|
||||
@@ -44,9 +44,6 @@ impl App {
|
||||
let event_pump: &'static mut EventPump =
|
||||
Box::leak(Box::new(sdl_context.event_pump().map_err(|e| GameError::Sdl(e.to_string()))?));
|
||||
|
||||
// Initialize platform-specific console
|
||||
get_platform().init_console()?;
|
||||
|
||||
let window = video_subsystem
|
||||
.window(
|
||||
"Pac-Man",
|
||||
|
||||
@@ -49,6 +49,26 @@ pub const CANVAS_SIZE: UVec2 = UVec2::new(
|
||||
(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.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
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 font_asset = RWops::from_bytes(static_font_data).map_err(|_| GameError::Sdl("Failed to load font".to_string()))?;
|
||||
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()))?;
|
||||
|
||||
// Initialize audio system
|
||||
@@ -213,7 +213,7 @@ impl Game {
|
||||
node: map.start_positions.pacman,
|
||||
},
|
||||
velocity: Velocity {
|
||||
speed: 1.15,
|
||||
speed: constants::mechanics::PLAYER_SPEED,
|
||||
direction: Direction::Left,
|
||||
},
|
||||
movement_modifiers: MovementModifiers::default(),
|
||||
@@ -226,7 +226,7 @@ impl Game {
|
||||
directional_animation: DirectionalAnimation::new(moving_tiles, stopped_tiles, 5),
|
||||
entity_type: EntityType::Player,
|
||||
collider: Collider {
|
||||
size: constants::CELL_SIZE as f32 * 1.375,
|
||||
size: constants::collider::PLAYER_GHOST_SIZE,
|
||||
},
|
||||
pacman_collider: PacmanCollider,
|
||||
};
|
||||
@@ -249,7 +249,10 @@ impl Game {
|
||||
world.insert_resource(DebugState::default());
|
||||
world.insert_resource(AudioState::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(event_pump);
|
||||
@@ -337,12 +340,12 @@ impl Game {
|
||||
.resource::<Map>()
|
||||
.iter_nodes()
|
||||
.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((
|
||||
*id,
|
||||
EntityType::PowerPellet,
|
||||
energizer_sprite,
|
||||
constants::CELL_SIZE as f32 * 0.95,
|
||||
constants::collider::POWER_PELLET_SIZE,
|
||||
)),
|
||||
_ => None,
|
||||
})
|
||||
@@ -360,7 +363,7 @@ impl Game {
|
||||
|
||||
// Make power pellets blink
|
||||
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,
|
||||
entity_type: EntityType::Ghost,
|
||||
collider: Collider {
|
||||
size: constants::CELL_SIZE as f32 * 1.375,
|
||||
size: constants::collider::PLAYER_GHOST_SIZE,
|
||||
},
|
||||
ghost_collider: GhostCollider,
|
||||
ghost_state: GhostState::Normal,
|
||||
|
||||
24
src/main.rs
24
src/main.rs
@@ -2,8 +2,6 @@
|
||||
|
||||
use crate::{app::App, constants::LOOP_TIME};
|
||||
use tracing::info;
|
||||
use tracing_error::ErrorLayer;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
|
||||
mod app;
|
||||
mod asset;
|
||||
@@ -23,14 +21,22 @@ mod texture;
|
||||
/// This function initializes SDL, the window, the game state, and then enters
|
||||
/// the main game loop.
|
||||
pub fn main() {
|
||||
// Setup tracing
|
||||
let subscriber = tracing_subscriber::fmt()
|
||||
.with_ansi(cfg!(not(target_os = "emscripten")))
|
||||
.with_max_level(tracing::Level::DEBUG)
|
||||
.finish()
|
||||
.with(ErrorLayer::default());
|
||||
// Setup buffered tracing subscriber that will buffer logs until console is ready
|
||||
let switchable_writer = platform::tracing_buffer::setup_switchable_subscriber();
|
||||
|
||||
tracing::subscriber::set_global_default(subscriber).expect("Could not set global default");
|
||||
// Log early to show buffering is working
|
||||
tracing::debug!("Tracing subscriber initialized with buffering - logs will be buffered until console is ready");
|
||||
|
||||
// Initialize platform-specific console
|
||||
tracing::debug!("Starting console initialization...");
|
||||
platform::get_platform().init_console().expect("Could not initialize console");
|
||||
tracing::debug!("Console initialization completed");
|
||||
|
||||
// Now that console is initialized, flush buffered logs and switch to direct output
|
||||
tracing::debug!("Switching to direct logging mode and flushing buffer...");
|
||||
if let Err(e) = switchable_writer.switch_to_direct_mode() {
|
||||
tracing::warn!("Failed to flush buffered logs to console: {}", e);
|
||||
}
|
||||
|
||||
let mut app = App::new().expect("Could not create app");
|
||||
|
||||
|
||||
55
src/platform/buffered_writer.rs
Normal file
55
src/platform/buffered_writer.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
//! Buffered writer for tracing logs that can store logs before console attachment.
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use std::io::{self, Write};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A thread-safe buffered writer that stores logs in memory until flushed.
|
||||
#[derive(Clone)]
|
||||
pub struct BufferedWriter {
|
||||
buffer: Arc<Mutex<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl BufferedWriter {
|
||||
/// Creates a new buffered writer.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
buffer: Arc::new(Mutex::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Flushes all buffered content to the provided writer and clears the buffer.
|
||||
pub fn flush_to<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
let mut buffer = self.buffer.lock();
|
||||
if !buffer.is_empty() {
|
||||
writer.write_all(&buffer)?;
|
||||
writer.flush()?;
|
||||
buffer.clear();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the current buffer size in bytes.
|
||||
pub fn buffer_size(&self) -> usize {
|
||||
self.buffer.lock().len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for BufferedWriter {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
let mut buffer = self.buffer.lock();
|
||||
buffer.extend_from_slice(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
// For buffered writer, flush is a no-op since we're storing in memory
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BufferedWriter {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ mod desktop;
|
||||
#[cfg(target_os = "emscripten")]
|
||||
mod emscripten;
|
||||
|
||||
pub mod buffered_writer;
|
||||
pub mod tracing_buffer;
|
||||
/// Cross-platform abstraction layer providing unified APIs for platform-specific operations.
|
||||
pub trait CommonPlatform {
|
||||
/// Platform-specific sleep function (required due to Emscripten's non-standard sleep requirements).
|
||||
|
||||
91
src/platform/tracing_buffer.rs
Normal file
91
src/platform/tracing_buffer.rs
Normal file
@@ -0,0 +1,91 @@
|
||||
//! Buffered tracing setup for handling logs before console attachment.
|
||||
|
||||
use crate::platform::buffered_writer::BufferedWriter;
|
||||
use std::io;
|
||||
use tracing::Level;
|
||||
use tracing_error::ErrorLayer;
|
||||
use tracing_subscriber::fmt::MakeWriter;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
|
||||
/// A writer that can switch between buffering and direct output.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct SwitchableWriter {
|
||||
buffered_writer: BufferedWriter,
|
||||
direct_mode: std::sync::Arc<parking_lot::Mutex<bool>>,
|
||||
}
|
||||
|
||||
impl SwitchableWriter {
|
||||
pub fn switch_to_direct_mode(&self) -> io::Result<()> {
|
||||
// Get buffer size before flushing for debug logging
|
||||
let buffer_size = self.buffered_writer.buffer_size();
|
||||
|
||||
// First flush any buffered content
|
||||
self.buffered_writer.flush_to(io::stdout())?;
|
||||
|
||||
// Switch to direct mode
|
||||
*self.direct_mode.lock() = true;
|
||||
|
||||
// Log how much was buffered (this will now go directly to stdout)
|
||||
tracing::debug!("Flushed {} bytes of buffered logs to console", buffer_size);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for SwitchableWriter {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
if *self.direct_mode.lock() {
|
||||
io::stdout().write(buf)
|
||||
} else {
|
||||
self.buffered_writer.clone().write(buf)
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
if *self.direct_mode.lock() {
|
||||
io::stdout().flush()
|
||||
} else {
|
||||
// For buffered mode, flush is a no-op
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A make writer that uses the switchable writer.
|
||||
#[derive(Clone)]
|
||||
pub struct SwitchableMakeWriter {
|
||||
writer: SwitchableWriter,
|
||||
}
|
||||
|
||||
impl SwitchableMakeWriter {
|
||||
pub fn new(writer: SwitchableWriter) -> Self {
|
||||
Self { writer }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MakeWriter<'a> for SwitchableMakeWriter {
|
||||
type Writer = SwitchableWriter;
|
||||
|
||||
fn make_writer(&'a self) -> Self::Writer {
|
||||
self.writer.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets up a switchable tracing subscriber that can transition from buffered to direct output.
|
||||
///
|
||||
/// Returns the switchable writer that can be used to control the behavior.
|
||||
pub fn setup_switchable_subscriber() -> SwitchableWriter {
|
||||
let switchable_writer = SwitchableWriter::default();
|
||||
let make_writer = SwitchableMakeWriter::new(switchable_writer.clone());
|
||||
|
||||
let _subscriber = tracing_subscriber::fmt()
|
||||
.with_ansi(cfg!(not(target_os = "emscripten")))
|
||||
.with_max_level(Level::DEBUG)
|
||||
.with_writer(make_writer)
|
||||
.finish()
|
||||
.with(ErrorLayer::default());
|
||||
|
||||
tracing::subscriber::set_global_default(_subscriber).expect("Could not set global default switchable subscriber");
|
||||
|
||||
switchable_writer
|
||||
}
|
||||
19
tests/tracing_buffer.rs
Normal file
19
tests/tracing_buffer.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
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();
|
||||
}
|
||||
Reference in New Issue
Block a user