From eead31d7fcbedc2b7b492f94ef27a6d665fc1115 Mon Sep 17 00:00:00 2001 From: Xevion Date: Wed, 23 Jul 2025 21:24:47 -0500 Subject: [PATCH] refactor: add 'glam' for better positioning types, drop position types --- Cargo.lock | 7 ++++ Cargo.toml | 1 + src/debug.rs | 31 ++++++++--------- src/edible.rs | 18 ++++++---- src/entity.rs | 61 ++++++++++++++++---------------- src/game.rs | 15 ++++---- src/ghost.rs | 65 ++++++++++++++++++----------------- src/ghosts/blinky.rs | 9 ++--- src/helper.rs | 82 ++++++++++++++++++++++++-------------------- src/map.rs | 80 +++++++++--------------------------------- src/pacman.rs | 20 ++++++----- 11 files changed, 183 insertions(+), 206 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 53fea5e..675c88b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,6 +77,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "glam" +version = "0.30.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50a99dbe56b72736564cfa4b85bf9a33079f16ae8b74983ab06af3b1a3696b11" + [[package]] name = "hashbrown" version = "0.15.4" @@ -171,6 +177,7 @@ name = "pacman" version = "0.1.0" dependencies = [ "anyhow", + "glam", "lazy_static", "libc", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index ac7d440..b477844 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ pathfinding = "4.14" once_cell = "1.21.3" thiserror = "1.0" anyhow = "1.0" +glam = "0.30.4" [target.'cfg(target_os = "windows")'.dependencies.winapi] version = "0.3" diff --git a/src/debug.rs b/src/debug.rs index c65b857..fce6184 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -4,6 +4,7 @@ use crate::{ ghosts::blinky::Blinky, map::Map, }; +use glam::{IVec2, UVec2}; use sdl2::{pixels::Color, render::Canvas, video::Window}; #[derive(PartialEq, Eq, Clone, Copy)] @@ -17,21 +18,22 @@ pub enum DebugMode { pub struct DebugRenderer; impl DebugRenderer { - pub fn draw_cell(canvas: &mut Canvas, _map: &Map, cell: (u32, u32), color: Color) { + pub fn draw_cell(canvas: &mut Canvas, _map: &Map, cell: UVec2, color: Color) { let position = Map::cell_to_pixel(cell); canvas.set_draw_color(color); canvas - .draw_rect(sdl2::rect::Rect::new(position.0, position.1, 24, 24)) + .draw_rect(sdl2::rect::Rect::new(position.x, position.y, 24, 24)) .expect("Could not draw rectangle"); } - pub fn draw_debug_grid(canvas: &mut Canvas, map: &Map, pacman_cell: (u32, u32)) { + pub fn draw_debug_grid(canvas: &mut Canvas, map: &Map, pacman_cell: UVec2) { for x in 0..BOARD_WIDTH { for y in 0..BOARD_HEIGHT { - let tile = map.get_tile((x as i32, y as i32)).unwrap_or(MapTile::Empty); + let tile = map.get_tile(IVec2::new(x as i32, y as i32)).unwrap_or(MapTile::Empty); + let cell = UVec2::new(x, y); let mut color = None; - if (x, y) == pacman_cell { - Self::draw_cell(canvas, map, (x, y), Color::CYAN); + if cell == pacman_cell { + Self::draw_cell(canvas, map, cell, Color::CYAN); } else { color = match tile { MapTile::Empty => None, @@ -43,31 +45,28 @@ impl DebugRenderer { }; } if let Some(color) = color { - Self::draw_cell(canvas, map, (x, y), color); + Self::draw_cell(canvas, map, cell, color); } } } } - pub fn draw_next_cell(canvas: &mut Canvas, map: &Map, next_cell: (u32, u32)) { + pub fn draw_next_cell(canvas: &mut Canvas, map: &Map, next_cell: UVec2) { Self::draw_cell(canvas, map, next_cell, Color::YELLOW); } pub fn draw_valid_positions(canvas: &mut Canvas, map: &mut Map) { let valid_positions_vec = map.get_valid_playable_positions().clone(); for &pos in &valid_positions_vec { - Self::draw_cell(canvas, map, (pos.x, pos.y), Color::RGB(255, 140, 0)); - // ORANGE + Self::draw_cell(canvas, map, pos, Color::RGB(255, 140, 0)); } } pub fn draw_pathfinding(canvas: &mut Canvas, blinky: &Blinky, map: &Map) { - if let Some((path, _)) = blinky.get_path_to_target({ - let (tx, ty) = blinky.get_target_tile(); - (tx as u32, ty as u32) - }) { - for &(x, y) in &path { - Self::draw_cell(canvas, map, (x, y), Color::YELLOW); + let target = blinky.get_target_tile(); + if let Some((path, _)) = blinky.get_path_to_target(target.as_uvec2()) { + for pos in &path { + Self::draw_cell(canvas, map, *pos, Color::YELLOW); } } } diff --git a/src/edible.rs b/src/edible.rs index b93f735..0d63028 100644 --- a/src/edible.rs +++ b/src/edible.rs @@ -4,6 +4,7 @@ use crate::constants::{FruitType, MapTile, BOARD_HEIGHT, BOARD_WIDTH}; use crate::direction::Direction; use crate::entity::{Entity, Renderable, StaticEntity}; use crate::map::Map; +use glam::{IVec2, UVec2}; use sdl2::{render::Canvas, video::Window}; use std::cell::RefCell; use std::rc::Rc; @@ -22,7 +23,7 @@ pub struct Edible<'a> { } impl<'a> Edible<'a> { - pub fn new(kind: EdibleKind, cell_position: (u32, u32), sprite: Rc>) -> Self { + pub fn new(kind: EdibleKind, cell_position: UVec2, sprite: Rc>) -> Self { let pixel_position = Map::cell_to_pixel(cell_position); Edible { base: StaticEntity::new(pixel_position, cell_position), @@ -45,8 +46,8 @@ impl<'a> Entity for Edible<'a> { impl<'a> Renderable for Edible<'a> { fn render(&self, canvas: &mut Canvas) { - self.sprite - .render(canvas, self.base.pixel_position, Direction::Right, Some(0)); + let pos = self.base.pixel_position; + self.sprite.render(canvas, (pos.x, pos.y), Direction::Right, Some(0)); } } @@ -60,14 +61,17 @@ pub fn reconstruct_edibles<'a>( let mut edibles = Vec::new(); for x in 0..BOARD_WIDTH { for y in 0..BOARD_HEIGHT { - let tile = map.borrow().get_tile((x as i32, y as i32)); - let cell = (x, y); + let tile = map.borrow().get_tile(IVec2::new(x as i32, y as i32)); match tile { Some(MapTile::Pellet) => { - edibles.push(Edible::new(EdibleKind::Pellet, cell, Rc::clone(&pellet_sprite))); + edibles.push(Edible::new(EdibleKind::Pellet, UVec2::new(x, y), Rc::clone(&pellet_sprite))); } Some(MapTile::PowerPellet) => { - edibles.push(Edible::new(EdibleKind::PowerPellet, cell, Rc::clone(&power_pellet_sprite))); + edibles.push(Edible::new( + EdibleKind::PowerPellet, + UVec2::new(x, y), + Rc::clone(&power_pellet_sprite), + )); } // Fruits can be added here if you have fruit positions _ => {} diff --git a/src/entity.rs b/src/entity.rs index 592531a..08b1967 100644 --- a/src/entity.rs +++ b/src/entity.rs @@ -4,6 +4,7 @@ use crate::{ map::Map, modulation::SimpleTickModulator, }; +use glam::{IVec2, UVec2}; use std::cell::RefCell; use std::rc::Rc; @@ -14,9 +15,9 @@ pub trait Entity { /// Returns true if the entity is colliding with the other entity. fn is_colliding(&self, other: &dyn Entity) -> bool { - let (x, y) = self.base().pixel_position; - let (other_x, other_y) = other.base().pixel_position; - x == other_x && y == other_y + let a = self.base().pixel_position; + let b = other.base().pixel_position; + a == b } } @@ -24,7 +25,7 @@ pub trait Entity { pub trait Moving { fn move_forward(&mut self); fn update_cell_position(&mut self); - fn next_cell(&self, direction: Option) -> (i32, i32); + fn next_cell(&self, direction: Option) -> IVec2; fn is_wall_ahead(&self, direction: Option) -> bool; fn handle_tunnel(&mut self) -> bool; fn is_grid_aligned(&self) -> bool; @@ -33,12 +34,12 @@ pub trait Moving { /// A struct for static (non-moving) entities with position only. pub struct StaticEntity { - pub pixel_position: (i32, i32), - pub cell_position: (u32, u32), + pub pixel_position: IVec2, + pub cell_position: UVec2, } impl StaticEntity { - pub fn new(pixel_position: (i32, i32), cell_position: (u32, u32)) -> Self { + pub fn new(pixel_position: IVec2, cell_position: UVec2) -> Self { Self { pixel_position, cell_position, @@ -58,8 +59,8 @@ pub struct MovableEntity { impl MovableEntity { pub fn new( - pixel_position: (i32, i32), - cell_position: (u32, u32), + pixel_position: IVec2, + cell_position: UVec2, direction: Direction, speed: u32, modulation: SimpleTickModulator, @@ -76,10 +77,10 @@ impl MovableEntity { } /// Returns the position within the current cell, in pixels. - pub fn internal_position(&self) -> (u32, u32) { - ( - self.base.pixel_position.0 as u32 % CELL_SIZE, - self.base.pixel_position.1 as u32 % CELL_SIZE, + pub fn internal_position(&self) -> UVec2 { + UVec2::new( + (self.base.pixel_position.x as u32) % CELL_SIZE, + (self.base.pixel_position.y as u32) % CELL_SIZE, ) } } @@ -94,21 +95,21 @@ impl Moving for MovableEntity { fn move_forward(&mut self) { let speed = self.speed as i32; match self.direction { - Direction::Right => self.base.pixel_position.0 += speed, - Direction::Left => self.base.pixel_position.0 -= speed, - Direction::Up => self.base.pixel_position.1 -= speed, - Direction::Down => self.base.pixel_position.1 += speed, + Direction::Right => self.base.pixel_position.x += speed, + Direction::Left => self.base.pixel_position.x -= speed, + Direction::Up => self.base.pixel_position.y -= speed, + Direction::Down => self.base.pixel_position.y += speed, } } fn update_cell_position(&mut self) { - self.base.cell_position = ( - (self.base.pixel_position.0 as u32 / CELL_SIZE) - BOARD_OFFSET.0, - (self.base.pixel_position.1 as u32 / CELL_SIZE) - BOARD_OFFSET.1, + self.base.cell_position = UVec2::new( + (self.base.pixel_position.x as u32 / CELL_SIZE) - BOARD_OFFSET.0, + (self.base.pixel_position.y as u32 / CELL_SIZE) - BOARD_OFFSET.1, ); } - fn next_cell(&self, direction: Option) -> (i32, i32) { + fn next_cell(&self, direction: Option) -> IVec2 { let (x, y) = direction.unwrap_or(self.direction).offset(); - (self.base.cell_position.0 as i32 + x, self.base.cell_position.1 as i32 + y) + IVec2::new(self.base.cell_position.x as i32 + x, self.base.cell_position.y as i32 + y) } fn is_wall_ahead(&self, direction: Option) -> bool { let next_cell = self.next_cell(direction); @@ -119,20 +120,20 @@ impl Moving for MovableEntity { let current_tile = self .map .borrow() - .get_tile((self.base.cell_position.0 as i32, self.base.cell_position.1 as i32)); + .get_tile(IVec2::new(self.base.cell_position.x as i32, self.base.cell_position.y as i32)); if matches!(current_tile, Some(MapTile::Tunnel)) { self.in_tunnel = true; } } if self.in_tunnel { - if self.base.cell_position.0 == 0 { - self.base.cell_position.0 = BOARD_WIDTH - 2; - self.base.pixel_position = Map::cell_to_pixel((self.base.cell_position.0, self.base.cell_position.1)); + if self.base.cell_position.x == 0 { + self.base.cell_position.x = BOARD_WIDTH - 2; + self.base.pixel_position = Map::cell_to_pixel(self.base.cell_position); self.in_tunnel = false; true - } else if self.base.cell_position.0 == BOARD_WIDTH - 1 { - self.base.cell_position.0 = 1; - self.base.pixel_position = Map::cell_to_pixel((self.base.cell_position.0, self.base.cell_position.1)); + } else if self.base.cell_position.x == BOARD_WIDTH - 1 { + self.base.cell_position.x = 1; + self.base.pixel_position = Map::cell_to_pixel(self.base.cell_position); self.in_tunnel = false; true } else { @@ -143,7 +144,7 @@ impl Moving for MovableEntity { } } fn is_grid_aligned(&self) -> bool { - self.internal_position() == (0, 0) + self.internal_position() == UVec2::ZERO } fn set_direction_if_valid(&mut self, new_direction: Direction) -> bool { if new_direction == self.direction { diff --git a/src/game.rs b/src/game.rs index d7ad6d2..ff58457 100644 --- a/src/game.rs +++ b/src/game.rs @@ -3,6 +3,7 @@ use std::cell::RefCell; use std::ops::Not; use std::rc::Rc; +use glam::UVec2; use rand::seq::IteratorRandom; use sdl2::image::LoadTexture; use sdl2::keyboard::Keycode; @@ -65,7 +66,7 @@ impl<'a> Game<'a> { let pacman_atlas = texture_creator .load_texture_bytes(&pacman_bytes) .expect("Could not load pacman texture from asset API"); - let pacman = Rc::new(RefCell::new(Pacman::new((1, 1), pacman_atlas, Rc::clone(&map)))); + let pacman = Rc::new(RefCell::new(Pacman::new(UVec2::new(1, 1), pacman_atlas, Rc::clone(&map)))); // Load ghost textures let ghost_body_bytes = get_asset_bytes(Asset::GhostBody).expect("Failed to load asset"); @@ -79,7 +80,7 @@ impl<'a> Game<'a> { // Create Blinky let blinky = Blinky::new( - (13, 11), // Starting position just above ghost house + UVec2::new(13, 11), // Starting position just above ghost house ghost_body, ghost_eyes, Rc::clone(&map), @@ -215,8 +216,8 @@ impl<'a> Game<'a> { // Randomize Pac-Man position if let Some(pos) = valid_positions.iter().choose(&mut rng) { let mut pacman = self.pacman.borrow_mut(); - pacman.base.base.pixel_position = Map::cell_to_pixel((pos.x, pos.y)); - pacman.base.base.cell_position = (pos.x, pos.y); + pacman.base.base.pixel_position = Map::cell_to_pixel(*pos); + pacman.base.base.cell_position = *pos; pacman.base.in_tunnel = false; pacman.base.direction = Direction::Right; pacman.next_direction = None; @@ -225,8 +226,8 @@ impl<'a> Game<'a> { // Randomize ghost position if let Some(pos) = valid_positions.iter().choose(&mut rng) { - self.blinky.base.base.pixel_position = Map::cell_to_pixel((pos.x, pos.y)); - self.blinky.base.base.cell_position = (pos.x, pos.y); + self.blinky.base.base.pixel_position = Map::cell_to_pixel(*pos); + self.blinky.base.base.cell_position = *pos; self.blinky.base.in_tunnel = false; self.blinky.base.direction = Direction::Left; self.blinky.mode = crate::ghost::GhostMode::Chase; @@ -308,7 +309,7 @@ impl<'a> Game<'a> { DebugMode::Grid => { DebugRenderer::draw_debug_grid(self.canvas, &self.map.borrow(), self.pacman.borrow().base.base.cell_position); let next_cell = ::next_cell(&*self.pacman.borrow(), None); - DebugRenderer::draw_next_cell(self.canvas, &self.map.borrow(), (next_cell.0 as u32, next_cell.1 as u32)); + DebugRenderer::draw_next_cell(self.canvas, &self.map.borrow(), next_cell.as_uvec2()); } DebugMode::ValidPositions => { DebugRenderer::draw_valid_positions(self.canvas, &mut self.map.borrow_mut()); diff --git a/src/ghost.rs b/src/ghost.rs index fd65d48..a3919c3 100644 --- a/src/ghost.rs +++ b/src/ghost.rs @@ -1,4 +1,3 @@ -use pathfinding::prelude::dijkstra; use rand::Rng; use crate::animation::{AnimatedAtlasTexture, FrameDrawn}; @@ -8,6 +7,7 @@ use crate::entity::{Entity, MovableEntity, Moving, Renderable}; use crate::map::Map; use crate::modulation::{SimpleTickModulator, TickModulator}; use crate::pacman::Pacman; +use glam::{IVec2, UVec2}; use sdl2::pixels::Color; use sdl2::render::Texture; use std::cell::RefCell; @@ -67,7 +67,7 @@ impl Ghost<'_> { /// Creates a new ghost instance pub fn new<'a>( ghost_type: GhostType, - starting_position: (u32, u32), + starting_position: UVec2, body_texture: Texture<'a>, eyes_texture: Texture<'a>, map: Rc>, @@ -95,7 +95,7 @@ impl Ghost<'_> { } /// Gets the target tile for this ghost based on its current mode - pub fn get_target_tile(&self) -> (i32, i32) { + pub fn get_target_tile(&self) -> IVec2 { match self.mode { GhostMode::Scatter => self.get_scatter_target(), GhostMode::Chase => self.get_chase_target(), @@ -106,17 +106,17 @@ impl Ghost<'_> { } /// Gets this ghost's home corner target for scatter mode - fn get_scatter_target(&self) -> (i32, i32) { + fn get_scatter_target(&self) -> IVec2 { match self.ghost_type { - GhostType::Blinky => (25, 0), // Top right - GhostType::Pinky => (2, 0), // Top left - GhostType::Inky => (27, 35), // Bottom right - GhostType::Clyde => (0, 35), // Bottom left + GhostType::Blinky => IVec2::new(25, 0), // Top right + GhostType::Pinky => IVec2::new(2, 0), // Top left + GhostType::Inky => IVec2::new(27, 35), // Bottom right + GhostType::Clyde => IVec2::new(0, 35), // Bottom left } } /// Gets a random adjacent tile for frightened mode - fn get_random_target(&self) -> (i32, i32) { + fn get_random_target(&self) -> IVec2 { let mut rng = rand::rng(); let mut possible_moves = Vec::new(); @@ -143,48 +143,49 @@ impl Ghost<'_> { } /// Gets the ghost house target for returning eyes - fn get_house_target(&self) -> (i32, i32) { - (13, 14) // Center of ghost house + fn get_house_target(&self) -> IVec2 { + IVec2::new(13, 14) // Center of ghost house } /// Gets the exit point target when leaving house - fn get_house_exit_target(&self) -> (i32, i32) { - (13, 11) // Just above ghost house + fn get_house_exit_target(&self) -> IVec2 { + IVec2::new(13, 11) // Just above ghost house } /// Gets this ghost's chase mode target (to be implemented by each ghost type) - fn get_chase_target(&self) -> (i32, i32) { + fn get_chase_target(&self) -> IVec2 { let pacman = self.pacman.borrow(); let cell = pacman.base().cell_position; - (cell.0 as i32, cell.1 as i32) + IVec2::new(cell.x as i32, cell.y as i32) } /// Calculates the path to the target tile using the A* algorithm. - pub fn get_path_to_target(&self, target: (u32, u32)) -> Option<(Vec<(u32, u32)>, u32)> { + pub fn get_path_to_target(&self, target: UVec2) -> Option<(Vec, u32)> { let start = self.base.base.cell_position; let map = self.base.map.borrow(); - + use pathfinding::prelude::dijkstra; dijkstra( &start, |&p| { let mut successors = vec![]; - let tile = map.get_tile((p.0 as i32, p.1 as i32)); + let tile = map.get_tile(IVec2::new(p.x as i32, p.y as i32)); // Tunnel wrap: if currently in a tunnel, add the opposite exit as a neighbor if let Some(MapTile::Tunnel) = tile { - if p.0 == 0 { - successors.push(((BOARD_WIDTH - 2, p.1), 1)); - } else if p.0 == BOARD_WIDTH - 1 { - successors.push(((1, p.1), 1)); + if p.x == 0 { + successors.push((UVec2::new(BOARD_WIDTH - 2, p.y), 1)); + } else if p.x == BOARD_WIDTH - 1 { + successors.push((UVec2::new(1, p.y), 1)); } } for dir in &[Direction::Up, Direction::Down, Direction::Left, Direction::Right] { let (dx, dy) = dir.offset(); - let next_p = (p.0 as i32 + dx, p.1 as i32 + dy); + let next_p = IVec2::new(p.x as i32 + dx, p.y as i32 + dy); if let Some(tile) = map.get_tile(next_p) { if tile == MapTile::Wall { continue; } - successors.push(((next_p.0 as u32, next_p.1 as u32), 1)); + let next_u = UVec2::new(next_p.x as u32, next_p.y as u32); + successors.push((next_u, 1)); } } successors @@ -226,12 +227,13 @@ impl Ghost<'_> { if !self.base.handle_tunnel() { // Pathfinding logic (only if not in tunnel) let target_tile = self.get_target_tile(); - if let Some((path, _)) = self.get_path_to_target((target_tile.0 as u32, target_tile.1 as u32)) { + if let Some((path, _)) = self.get_path_to_target(target_tile.as_uvec2()) { if path.len() > 1 { let next_move = path[1]; - let (x, y) = self.base.base.cell_position; - let dx = next_move.0 as i32 - x as i32; - let dy = next_move.1 as i32 - y as i32; + let x = self.base.base.cell_position.x; + let y = self.base.base.cell_position.y; + let dx = next_move.x as i32 - x as i32; + let dy = next_move.y as i32 - y as i32; let new_direction = if dx > 0 { Direction::Right } else if dx < 0 { @@ -269,7 +271,7 @@ impl<'a> Moving for Ghost<'a> { fn update_cell_position(&mut self) { self.base.update_cell_position(); } - fn next_cell(&self, direction: Option) -> (i32, i32) { + fn next_cell(&self, direction: Option) -> IVec2 { self.base.next_cell(direction) } fn is_wall_ahead(&self, direction: Option) -> bool { @@ -289,7 +291,7 @@ impl<'a> Moving for Ghost<'a> { impl<'a> Renderable for Ghost<'a> { fn render(&self, canvas: &mut sdl2::render::Canvas) { let pos = self.base.base.pixel_position; - self.body_sprite.render(canvas, pos, Direction::Right, None); + self.body_sprite.render(canvas, (pos.x, pos.y), Direction::Right, None); // Inline the eye_frame logic here let eye_frame = if self.mode == GhostMode::Frightened { 4 // Frightened frame @@ -301,6 +303,7 @@ impl<'a> Renderable for Ghost<'a> { Direction::Down => 3, } }; - self.eyes_sprite.render(canvas, pos, Direction::Right, Some(eye_frame)); + self.eyes_sprite + .render(canvas, (pos.x, pos.y), Direction::Right, Some(eye_frame)); } } diff --git a/src/ghosts/blinky.rs b/src/ghosts/blinky.rs index f24a0df..4815f34 100644 --- a/src/ghosts/blinky.rs +++ b/src/ghosts/blinky.rs @@ -9,6 +9,7 @@ use crate::entity::{Entity, Moving, Renderable, StaticEntity}; use crate::ghost::{Ghost, GhostMode, GhostType}; use crate::map::Map; use crate::pacman::Pacman; +use glam::{IVec2, UVec2}; pub struct Blinky<'a> { ghost: Ghost<'a>, @@ -16,7 +17,7 @@ pub struct Blinky<'a> { impl<'a> Blinky<'a> { pub fn new( - starting_position: (u32, u32), + starting_position: UVec2, body_texture: Texture<'a>, eyes_texture: Texture<'a>, map: Rc>, @@ -28,10 +29,10 @@ impl<'a> Blinky<'a> { } /// Gets Blinky's chase target - directly targets Pac-Man's current position - fn get_chase_target(&self) -> (i32, i32) { + fn get_chase_target(&self) -> IVec2 { let pacman = self.ghost.pacman.borrow(); let cell = pacman.base().cell_position; - (cell.0 as i32, cell.1 as i32) + IVec2::new(cell.x as i32, cell.y as i32) } pub fn set_mode(&mut self, mode: GhostMode) { @@ -62,7 +63,7 @@ impl<'a> Moving for Blinky<'a> { fn update_cell_position(&mut self) { self.ghost.update_cell_position(); } - fn next_cell(&self, direction: Option) -> (i32, i32) { + fn next_cell(&self, direction: Option) -> IVec2 { self.ghost.next_cell(direction) } fn is_wall_ahead(&self, direction: Option) -> bool { diff --git a/src/helper.rs b/src/helper.rs index 0c45786..93db48f 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -1,5 +1,7 @@ //! This module contains helper functions that are used throughout the game. +use glam::UVec2; + /// Checks if two grid positions are adjacent to each other /// /// # Arguments @@ -10,11 +12,9 @@ /// # Returns /// * `true` if positions are adjacent according to the diagonal parameter /// * `false` otherwise -pub fn is_adjacent(a: (u32, u32), b: (u32, u32), diagonal: bool) -> bool { - let (ax, ay) = a; - let (bx, by) = b; - let dx = ax.abs_diff(bx); - let dy = ay.abs_diff(by); +pub fn is_adjacent(a: UVec2, b: UVec2, diagonal: bool) -> bool { + let dx = a.x.abs_diff(b.x); + let dy = a.y.abs_diff(b.y); if diagonal { dx <= 1 && dy <= 1 && (dx != 0 || dy != 0) } else { @@ -31,22 +31,22 @@ mod tests { // Test orthogonal adjacency (diagonal = false) // Same position should not be adjacent - assert!(!is_adjacent((0, 0), (0, 0), false)); + assert!(!is_adjacent(UVec2::new(0, 0), UVec2::new(0, 0), false)); // Adjacent positions should be true - assert!(is_adjacent((0, 0), (1, 0), false)); // Right - assert!(is_adjacent((0, 0), (0, 1), false)); // Down - assert!(is_adjacent((1, 1), (0, 1), false)); // Left - assert!(is_adjacent((1, 1), (1, 0), false)); // Up + assert!(is_adjacent(UVec2::new(0, 0), UVec2::new(1, 0), false)); // Right + assert!(is_adjacent(UVec2::new(0, 0), UVec2::new(0, 1), false)); // Down + assert!(is_adjacent(UVec2::new(1, 1), UVec2::new(0, 1), false)); // Left + assert!(is_adjacent(UVec2::new(1, 1), UVec2::new(1, 0), false)); // Up // Diagonal positions should be false - assert!(!is_adjacent((0, 0), (1, 1), false)); - assert!(!is_adjacent((0, 1), (1, 0), false)); + assert!(!is_adjacent(UVec2::new(0, 0), UVec2::new(1, 1), false)); + assert!(!is_adjacent(UVec2::new(0, 1), UVec2::new(1, 0), false)); // Positions more than 1 step away should be false - assert!(!is_adjacent((0, 0), (2, 0), false)); - assert!(!is_adjacent((0, 0), (0, 2), false)); - assert!(!is_adjacent((0, 0), (2, 2), false)); + assert!(!is_adjacent(UVec2::new(0, 0), UVec2::new(2, 0), false)); + assert!(!is_adjacent(UVec2::new(0, 0), UVec2::new(0, 2), false)); + assert!(!is_adjacent(UVec2::new(0, 0), UVec2::new(2, 2), false)); } #[test] @@ -54,48 +54,54 @@ mod tests { // Test diagonal adjacency (diagonal = true) // Same position should not be adjacent - assert!(!is_adjacent((0, 0), (0, 0), true)); + assert!(!is_adjacent(UVec2::new(0, 0), UVec2::new(0, 0), true)); // Orthogonal adjacent positions should be true - assert!(is_adjacent((0, 0), (1, 0), true)); // Right - assert!(is_adjacent((0, 0), (0, 1), true)); // Down - assert!(is_adjacent((1, 1), (0, 1), true)); // Left - assert!(is_adjacent((1, 1), (1, 0), true)); // Up + assert!(is_adjacent(UVec2::new(0, 0), UVec2::new(1, 0), true)); // Right + assert!(is_adjacent(UVec2::new(0, 0), UVec2::new(0, 1), true)); // Down + assert!(is_adjacent(UVec2::new(1, 1), UVec2::new(0, 1), true)); // Left + assert!(is_adjacent(UVec2::new(1, 1), UVec2::new(1, 0), true)); // Up // Diagonal adjacent positions should be true - assert!(is_adjacent((0, 0), (1, 1), true)); // Down-right - assert!(is_adjacent((1, 0), (0, 1), true)); // Down-left - assert!(is_adjacent((0, 1), (1, 0), true)); // Up-right - assert!(is_adjacent((1, 1), (0, 0), true)); // Up-left + assert!(is_adjacent(UVec2::new(0, 0), UVec2::new(1, 1), true)); // Down-right + assert!(is_adjacent(UVec2::new(1, 0), UVec2::new(0, 1), true)); // Down-left + assert!(is_adjacent(UVec2::new(0, 1), UVec2::new(1, 0), true)); // Up-right + assert!(is_adjacent(UVec2::new(1, 1), UVec2::new(0, 0), true)); // Up-left // Positions more than 1 step away should be false - assert!(!is_adjacent((0, 0), (2, 0), true)); - assert!(!is_adjacent((0, 0), (0, 2), true)); - assert!(!is_adjacent((0, 0), (2, 2), true)); - assert!(!is_adjacent((0, 0), (1, 2), true)); + assert!(!is_adjacent(UVec2::new(0, 0), UVec2::new(2, 0), true)); + assert!(!is_adjacent(UVec2::new(0, 0), UVec2::new(0, 2), true)); + assert!(!is_adjacent(UVec2::new(0, 0), UVec2::new(2, 2), true)); + assert!(!is_adjacent(UVec2::new(0, 0), UVec2::new(1, 2), true)); } #[test] fn test_edge_cases() { // Test with larger coordinates - assert!(is_adjacent((100, 100), (101, 100), false)); - assert!(is_adjacent((100, 100), (100, 101), false)); - assert!(!is_adjacent((100, 100), (102, 100), false)); + assert!(is_adjacent(UVec2::new(100, 100), UVec2::new(101, 100), false)); + assert!(is_adjacent(UVec2::new(100, 100), UVec2::new(100, 101), false)); + assert!(!is_adjacent(UVec2::new(100, 100), UVec2::new(102, 100), false)); - assert!(is_adjacent((100, 100), (101, 101), true)); - assert!(!is_adjacent((100, 100), (102, 102), true)); + assert!(is_adjacent(UVec2::new(100, 100), UVec2::new(101, 101), true)); + assert!(!is_adjacent(UVec2::new(100, 100), UVec2::new(102, 102), true)); // Test with zero coordinates - assert!(is_adjacent((0, 0), (1, 0), false)); - assert!(is_adjacent((0, 0), (0, 1), false)); - assert!(is_adjacent((0, 0), (1, 1), true)); + assert!(is_adjacent(UVec2::new(0, 0), UVec2::new(1, 0), false)); + assert!(is_adjacent(UVec2::new(0, 0), UVec2::new(0, 1), false)); + assert!(is_adjacent(UVec2::new(0, 0), UVec2::new(1, 1), true)); } #[test] fn test_commutative_property() { // The function should work the same regardless of parameter order - assert_eq!(is_adjacent((1, 2), (2, 2), false), is_adjacent((2, 2), (1, 2), false)); + assert_eq!( + is_adjacent(UVec2::new(1, 2), UVec2::new(2, 2), false), + is_adjacent(UVec2::new(2, 2), UVec2::new(1, 2), false) + ); - assert_eq!(is_adjacent((1, 2), (2, 3), true), is_adjacent((2, 3), (1, 2), true)); + assert_eq!( + is_adjacent(UVec2::new(1, 2), UVec2::new(2, 3), true), + is_adjacent(UVec2::new(2, 3), UVec2::new(1, 2), true) + ); } } diff --git a/src/map.rs b/src/map.rs index 72b44f8..c637514 100644 --- a/src/map.rs +++ b/src/map.rs @@ -3,49 +3,9 @@ use rand::seq::IteratorRandom; use crate::constants::{MapTile, BOARD_OFFSET, CELL_SIZE}; use crate::constants::{BOARD_HEIGHT, BOARD_WIDTH}; +use glam::{IVec2, UVec2}; use once_cell::sync::OnceCell; use std::collections::{HashSet, VecDeque}; -use std::ops::Add; - -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct SignedPosition { - pub x: i32, - pub y: i32, -} - -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct Position { - pub x: u32, - pub y: u32, -} - -impl Add for Position { - type Output = Position; - fn add(self, rhs: SignedPosition) -> Self::Output { - Position { - x: (self.x as i32 + rhs.x) as u32, - y: (self.y as i32 + rhs.y) as u32, - } - } -} - -impl PartialEq<(u32, u32)> for Position { - fn eq(&self, other: &(u32, u32)) -> bool { - self.x == other.0 && self.y == other.1 - } -} - -impl Position { - pub fn as_i32(&self) -> (i32, i32) { - (self.x as i32, self.y as i32) - } - pub fn wrapping_add(&self, dx: i32, dy: i32) -> Position { - Position { - x: (self.x as i32 + dx) as u32, - y: (self.y as i32 + dy) as u32, - } - } -} /// The game map. /// @@ -104,9 +64,9 @@ impl Map { /// # Arguments /// /// * `cell` - The cell coordinates, in grid coordinates. - pub fn get_tile(&self, cell: (i32, i32)) -> Option { - let x = cell.0 as usize; - let y = cell.1 as usize; + pub fn get_tile(&self, cell: IVec2) -> Option { + let x = cell.x as usize; + let y = cell.y as usize; if x >= BOARD_WIDTH as usize || y >= BOARD_HEIGHT as usize { return None; @@ -121,9 +81,9 @@ impl Map { /// /// * `cell` - The cell coordinates, in grid coordinates. /// * `tile` - The tile to set. - pub fn set_tile(&mut self, cell: (i32, i32), tile: MapTile) -> bool { - let x = cell.0 as usize; - let y = cell.1 as usize; + pub fn set_tile(&mut self, cell: IVec2, tile: MapTile) -> bool { + let x = cell.x as usize; + let y = cell.y as usize; if x >= BOARD_WIDTH as usize || y >= BOARD_HEIGHT as usize { return false; @@ -138,15 +98,15 @@ impl Map { /// # Arguments /// /// * `cell` - The cell coordinates, in grid coordinates. - pub fn cell_to_pixel(cell: (u32, u32)) -> (i32, i32) { - ((cell.0 * CELL_SIZE) as i32, ((cell.1 + BOARD_OFFSET.1) * CELL_SIZE) as i32) + pub fn cell_to_pixel(cell: UVec2) -> IVec2 { + IVec2::new((cell.x * CELL_SIZE) as i32, ((cell.y + BOARD_OFFSET.1) * CELL_SIZE) as i32) } /// Returns a reference to a cached vector of all valid playable positions in the maze. /// This is computed once using a flood fill from a random pellet, and then cached. - pub fn get_valid_playable_positions(&mut self) -> &Vec { + pub fn get_valid_playable_positions(&mut self) -> &Vec { use MapTile::*; - static CACHE: OnceCell> = OnceCell::new(); + static CACHE: OnceCell> = OnceCell::new(); if let Some(cached) = CACHE.get() { return cached; } @@ -155,10 +115,7 @@ impl Map { for (x, col) in self.current.iter().enumerate().take(BOARD_WIDTH as usize) { for (y, &cell) in col.iter().enumerate().take(BOARD_HEIGHT as usize) { match cell { - Pellet | PowerPellet => pellet_positions.push(Position { - x: x as u32, - y: y as u32, - }), + Pellet | PowerPellet => pellet_positions.push(UVec2::new(x as u32, y as u32)), _ => {} } } @@ -180,13 +137,8 @@ impl Map { match self.current[pos.x as usize][pos.y as usize] { Empty | Pellet | PowerPellet => { - for offset in [ - SignedPosition { x: -1, y: 0 }, - SignedPosition { x: 1, y: 0 }, - SignedPosition { x: 0, y: -1 }, - SignedPosition { x: 0, y: 1 }, - ] { - let neighbor = pos + offset; + for offset in [IVec2::new(-1, 0), IVec2::new(1, 0), IVec2::new(0, -1), IVec2::new(0, 1)] { + let neighbor = (pos.as_ivec2() + offset).as_uvec2(); if neighbor.x < BOARD_WIDTH && neighbor.y < BOARD_HEIGHT { let neighbor_tile = self.current[neighbor.x as usize][neighbor.y as usize]; if matches!(neighbor_tile, Empty | Pellet | PowerPellet) { @@ -198,8 +150,8 @@ impl Map { StartingPosition(_) | Wall | Tunnel => {} } } - let mut result: Vec = visited.into_iter().collect(); - result.sort_unstable(); + let mut result: Vec = visited.into_iter().collect(); + result.sort_unstable_by_key(|v| (v.x, v.y)); CACHE.get_or_init(|| result) } } diff --git a/src/pacman.rs b/src/pacman.rs index f12b6ee..71238e0 100644 --- a/src/pacman.rs +++ b/src/pacman.rs @@ -15,6 +15,8 @@ use crate::{ modulation::{SimpleTickModulator, TickModulator}, }; +use glam::{IVec2, UVec2}; + /// The Pac-Man entity. pub struct Pacman<'a> { /// Shared movement and position fields. @@ -39,7 +41,7 @@ impl<'a> Moving for Pacman<'a> { fn update_cell_position(&mut self) { self.base.update_cell_position(); } - fn next_cell(&self, direction: Option) -> (i32, i32) { + fn next_cell(&self, direction: Option) -> IVec2 { self.base.next_cell(direction) } fn is_wall_ahead(&self, direction: Option) -> bool { @@ -58,7 +60,7 @@ impl<'a> Moving for Pacman<'a> { impl Pacman<'_> { /// Creates a new `Pacman` instance. - pub fn new<'a>(starting_position: (u32, u32), atlas: Texture<'a>, map: Rc>) -> Pacman<'a> { + pub fn new<'a>(starting_position: UVec2, atlas: Texture<'a>, map: Rc>) -> Pacman<'a> { let pixel_position = Map::cell_to_pixel(starting_position); Pacman { base: MovableEntity::new( @@ -90,13 +92,13 @@ impl Pacman<'_> { } /// Returns the internal position of Pac-Man, rounded down to the nearest even number. - fn internal_position_even(&self) -> (u32, u32) { - let (x, y) = self.base.internal_position(); - ((x / 2u32) * 2u32, (y / 2u32) * 2u32) + fn internal_position_even(&self) -> UVec2 { + let pos = self.base.internal_position(); + UVec2::new((pos.x / 2) * 2, (pos.y / 2) * 2) } pub fn tick(&mut self) { - let can_change = self.internal_position_even() == (0, 0); + let can_change = self.internal_position_even() == UVec2::ZERO; if can_change { ::update_cell_position(self); if !::handle_tunnel(self) { @@ -110,7 +112,7 @@ impl Pacman<'_> { } if !self.stopped && self.base.modulation.next() { ::move_forward(self); - if self.internal_position_even() == (0, 0) { + if self.internal_position_even() == UVec2::ZERO { ::update_cell_position(self); } } @@ -122,9 +124,9 @@ impl Renderable for Pacman<'_> { let pos = self.base.base.pixel_position; let dir = self.base.direction; if self.stopped { - self.sprite.render(canvas, pos, dir, Some(2)); + self.sprite.render(canvas, (pos.x, pos.y), dir, Some(2)); } else { - self.sprite.render(canvas, pos, dir, None); + self.sprite.render(canvas, (pos.x, pos.y), dir, None); } } }