mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-10 08:07:56 -06:00
Compare commits
2 Commits
v0.37.2
...
2f1ff85d8f
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f1ff85d8f | |||
| b7429cd9ec |
48
src/app.rs
48
src/app.rs
@@ -6,7 +6,7 @@ use sdl2::render::{Canvas, ScaleMode, Texture, TextureCreator};
|
|||||||
use sdl2::ttf::Sdl2TtfContext;
|
use sdl2::ttf::Sdl2TtfContext;
|
||||||
use sdl2::video::{Window, WindowContext};
|
use sdl2::video::{Window, WindowContext};
|
||||||
use sdl2::{AudioSubsystem, EventPump, Sdl, VideoSubsystem};
|
use sdl2::{AudioSubsystem, EventPump, Sdl, VideoSubsystem};
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
use crate::error::{GameError, GameResult};
|
use crate::error::{GameError, GameResult};
|
||||||
|
|
||||||
@@ -20,11 +20,11 @@ pub struct App {
|
|||||||
game: Game,
|
game: Game,
|
||||||
input_system: InputSystem,
|
input_system: InputSystem,
|
||||||
canvas: Canvas<Window>,
|
canvas: Canvas<Window>,
|
||||||
event_pump: &'static mut EventPump,
|
|
||||||
backbuffer: Texture<'static>,
|
backbuffer: Texture<'static>,
|
||||||
paused: bool,
|
event_pump: &'static mut EventPump,
|
||||||
focused: bool,
|
|
||||||
last_tick: Instant,
|
last_tick: Instant,
|
||||||
|
focused: bool,
|
||||||
cursor_pos: Vec2,
|
cursor_pos: Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +87,6 @@ impl App {
|
|||||||
canvas,
|
canvas,
|
||||||
event_pump,
|
event_pump,
|
||||||
backbuffer,
|
backbuffer,
|
||||||
paused: false,
|
|
||||||
focused: true,
|
focused: true,
|
||||||
last_tick: Instant::now(),
|
last_tick: Instant::now(),
|
||||||
cursor_pos: Vec2::ZERO,
|
cursor_pos: Vec2::ZERO,
|
||||||
@@ -105,17 +104,10 @@ impl App {
|
|||||||
self.focused = true;
|
self.focused = true;
|
||||||
}
|
}
|
||||||
WindowEvent::FocusLost => {
|
WindowEvent::FocusLost => {
|
||||||
debug!("Window focus lost");
|
|
||||||
self.focused = false;
|
self.focused = false;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
// It doesn't really make sense to have this available in the browser
|
|
||||||
#[cfg(not(target_os = "emscripten"))]
|
|
||||||
Event::Quit { .. } => {
|
|
||||||
info!("Exit requested. Exiting...");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Event::MouseMotion { x, y, .. } => {
|
Event::MouseMotion { x, y, .. } => {
|
||||||
// Convert window coordinates to logical coordinates
|
// Convert window coordinates to logical coordinates
|
||||||
self.cursor_pos = Vec2::new(x as f32, y as f32);
|
self.cursor_pos = Vec2::new(x as f32, y as f32);
|
||||||
@@ -123,17 +115,12 @@ impl App {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
let commands = self.input_system.handle_event(&event);
|
if let Some(command) = self.input_system.handle_event(&event) {
|
||||||
for command in commands {
|
|
||||||
match command {
|
match command {
|
||||||
GameCommand::Exit => {
|
GameCommand::Exit => {
|
||||||
info!("Exit requested. Exiting...");
|
info!("Exit requested. Exiting...");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
GameCommand::TogglePause => {
|
|
||||||
self.paused = !self.paused;
|
|
||||||
info!("{}", if self.paused { "Paused" } else { "Unpaused" });
|
|
||||||
}
|
|
||||||
_ => self.game.post_event(command.into()),
|
_ => self.game.post_event(command.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,17 +129,20 @@ impl App {
|
|||||||
let dt = self.last_tick.elapsed().as_secs_f32();
|
let dt = self.last_tick.elapsed().as_secs_f32();
|
||||||
self.last_tick = Instant::now();
|
self.last_tick = Instant::now();
|
||||||
|
|
||||||
if !self.paused {
|
let exit = self.game.tick(dt);
|
||||||
self.game.tick(dt);
|
|
||||||
if let Err(e) = self.game.draw(&mut self.canvas, &mut self.backbuffer) {
|
if exit {
|
||||||
error!("Failed to draw game: {}", e);
|
return false;
|
||||||
}
|
}
|
||||||
if let Err(e) = self
|
|
||||||
.game
|
if let Err(e) = self.game.draw(&mut self.canvas, &mut self.backbuffer) {
|
||||||
.present_backbuffer(&mut self.canvas, &self.backbuffer, self.cursor_pos)
|
error!("Failed to draw game: {}", e);
|
||||||
{
|
}
|
||||||
error!("Failed to present backbuffer: {}", e);
|
if let Err(e) = self
|
||||||
}
|
.game
|
||||||
|
.present_backbuffer(&mut self.canvas, &self.backbuffer, self.cursor_pos)
|
||||||
|
{
|
||||||
|
error!("Failed to present backbuffer: {}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if start.elapsed() < LOOP_TIME {
|
if start.elapsed() < LOOP_TIME {
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ use crate::input::commands::GameCommand;
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum GameEvent {
|
pub enum GameEvent {
|
||||||
InputCommand(GameCommand),
|
Command(GameCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<GameCommand> for GameEvent {
|
impl From<GameCommand> for GameEvent {
|
||||||
fn from(command: GameCommand) -> Self {
|
fn from(command: GameCommand) -> Self {
|
||||||
GameEvent::InputCommand(command)
|
GameEvent::Command(command)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,16 +60,17 @@ impl Game {
|
|||||||
tracing::error!("Failed to reset game state: {}", e);
|
tracing::error!("Failed to reset game state: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GameCommand::Exit | GameCommand::TogglePause => {
|
GameCommand::TogglePause => {
|
||||||
// These are handled in app.rs
|
self.state.paused = !self.state.paused;
|
||||||
}
|
}
|
||||||
|
GameCommand::Exit => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_events(&mut self) {
|
fn process_events(&mut self) {
|
||||||
while let Some(event) = self.state.event_queue.pop_front() {
|
while let Some(event) = self.state.event_queue.pop_front() {
|
||||||
match event {
|
match event {
|
||||||
GameEvent::InputCommand(command) => self.handle_command(command),
|
GameEvent::Command(command) => self.handle_command(command),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,8 +115,18 @@ impl Game {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(&mut self, dt: f32) {
|
/// Ticks the game state.
|
||||||
|
///
|
||||||
|
/// Returns true if the game should exit.
|
||||||
|
pub fn tick(&mut self, dt: f32) -> bool {
|
||||||
|
// Process any events that have been posted (such as unpausing)
|
||||||
self.process_events();
|
self.process_events();
|
||||||
|
|
||||||
|
// If the game is paused, we don't need to do anything beyond returning
|
||||||
|
if self.state.paused {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
self.state.pacman.tick(dt, &self.state.map.graph);
|
self.state.pacman.tick(dt, &self.state.map.graph);
|
||||||
|
|
||||||
// Update all ghosts
|
// Update all ghosts
|
||||||
@@ -128,6 +139,8 @@ impl Game {
|
|||||||
|
|
||||||
// Check for collisions
|
// Check for collisions
|
||||||
self.check_collisions();
|
self.check_collisions();
|
||||||
|
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Toggles the debug mode on and off.
|
/// Toggles the debug mode on and off.
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ include!(concat!(env!("OUT_DIR"), "/atlas_data.rs"));
|
|||||||
/// we can cleanly separate it from the game's logic, making it easier to manage
|
/// we can cleanly separate it from the game's logic, making it easier to manage
|
||||||
/// and reason about.
|
/// and reason about.
|
||||||
pub struct GameState {
|
pub struct GameState {
|
||||||
|
pub paused: bool,
|
||||||
|
|
||||||
pub score: u32,
|
pub score: u32,
|
||||||
pub map: Map,
|
pub map: Map,
|
||||||
pub pacman: Pacman,
|
pub pacman: Pacman,
|
||||||
@@ -128,6 +130,7 @@ impl GameState {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
paused: false,
|
||||||
map,
|
map,
|
||||||
atlas,
|
atlas,
|
||||||
pacman,
|
pacman,
|
||||||
|
|||||||
@@ -36,13 +36,12 @@ impl InputSystem {
|
|||||||
Self { key_bindings }
|
Self { key_bindings }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_event(&self, event: &Event) -> Vec<GameCommand> {
|
/// Handles an event and returns a command if one is bound to the event.
|
||||||
let mut commands = Vec::new();
|
pub fn handle_event(&self, event: &Event) -> Option<GameCommand> {
|
||||||
if let Event::KeyDown { keycode: Some(key), .. } = event {
|
match event {
|
||||||
if let Some(command) = self.key_bindings.get(key) {
|
Event::Quit { .. } => Some(GameCommand::Exit),
|
||||||
commands.push(*command);
|
Event::KeyDown { keycode: Some(key), .. } => self.key_bindings.get(key).copied(),
|
||||||
}
|
_ => None,
|
||||||
}
|
}
|
||||||
commands
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use pacman::constants::RAW_BOARD;
|
use pacman::constants::RAW_BOARD;
|
||||||
use pacman::map::Map;
|
use pacman::map::builder::Map;
|
||||||
|
|
||||||
mod collision;
|
mod collision;
|
||||||
mod item;
|
mod item;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use glam::Vec2;
|
use glam::Vec2;
|
||||||
use pacman::constants::{CELL_SIZE, RAW_BOARD};
|
use pacman::constants::{CELL_SIZE, RAW_BOARD};
|
||||||
use pacman::map::Map;
|
use pacman::map::builder::Map;
|
||||||
use sdl2::render::Texture;
|
use sdl2::render::Texture;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use pacman::entity::direction::Direction;
|
|||||||
use pacman::entity::graph::{Graph, Node};
|
use pacman::entity::graph::{Graph, Node};
|
||||||
use pacman::entity::pacman::Pacman;
|
use pacman::entity::pacman::Pacman;
|
||||||
use pacman::texture::sprite::{AtlasMapper, MapperFrame, SpriteAtlas};
|
use pacman::texture::sprite::{AtlasMapper, MapperFrame, SpriteAtlas};
|
||||||
use sdl2::keyboard::Keycode;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
fn create_test_graph() -> Graph {
|
fn create_test_graph() -> Graph {
|
||||||
@@ -72,36 +71,3 @@ fn test_pacman_creation() {
|
|||||||
assert!(pacman.traverser.position.is_at_node());
|
assert!(pacman.traverser.position.is_at_node());
|
||||||
assert_eq!(pacman.traverser.direction, Direction::Left);
|
assert_eq!(pacman.traverser.direction, Direction::Left);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_pacman_key_handling() {
|
|
||||||
let graph = create_test_graph();
|
|
||||||
let atlas = create_test_atlas();
|
|
||||||
let mut pacman = Pacman::new(&graph, 0, &atlas).unwrap();
|
|
||||||
|
|
||||||
let test_cases = [
|
|
||||||
(Keycode::Up, Direction::Up),
|
|
||||||
(Keycode::Down, Direction::Down),
|
|
||||||
(Keycode::Left, Direction::Left),
|
|
||||||
(Keycode::Right, Direction::Right),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (key, expected_direction) in test_cases {
|
|
||||||
pacman.handle_key(key);
|
|
||||||
assert!(pacman.traverser.next_direction.is_some() || pacman.traverser.direction == expected_direction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_pacman_invalid_key() {
|
|
||||||
let graph = create_test_graph();
|
|
||||||
let atlas = create_test_atlas();
|
|
||||||
let mut pacman = Pacman::new(&graph, 0, &atlas).unwrap();
|
|
||||||
|
|
||||||
let original_direction = pacman.traverser.direction;
|
|
||||||
let original_next_direction = pacman.traverser.next_direction;
|
|
||||||
|
|
||||||
pacman.handle_key(Keycode::Space);
|
|
||||||
assert_eq!(pacman.traverser.direction, original_direction);
|
|
||||||
assert_eq!(pacman.traverser.next_direction, original_next_direction);
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user