mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-06 13:15:47 -06:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f1e1d4d42 | ||
|
|
9e029966dc | ||
|
|
968eb39b64 |
5
Justfile
5
Justfile
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ fn test_atlas_mapper_multiple_frames() {
|
|||||||
assert!(mapper.frames.contains_key("tile1"));
|
assert!(mapper.frames.contains_key("tile1"));
|
||||||
assert!(mapper.frames.contains_key("tile2"));
|
assert!(mapper.frames.contains_key("tile2"));
|
||||||
assert!(!mapper.frames.contains_key("tile3"));
|
assert!(!mapper.frames.contains_key("tile3"));
|
||||||
assert!(mapper.frames.get("nonexistent").is_none());
|
assert!(!mapper.frames.contains_key("nonexistent"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
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