mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-06 15:15:48 -06:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 320da36b83 | |||
| b68813cf5b | |||
| 0806fc744c | |||
| eead31d7fc |
@@ -5,10 +5,12 @@ on: [push]
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
RUST_TOOLCHAIN: 1.88.0
|
||||
|
||||
jobs:
|
||||
wasm:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
pages: write
|
||||
id-token: write
|
||||
@@ -23,9 +25,10 @@ jobs:
|
||||
version: 3.1.43
|
||||
|
||||
- name: Setup Rust (WASM32 Emscripten)
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
target: wasm32-unknown-emscripten
|
||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
@@ -37,7 +40,7 @@ jobs:
|
||||
run_install: true
|
||||
|
||||
- name: Build
|
||||
run: ./scripts/build.sh -er # release mode, skip emsdk
|
||||
run: ./build.sh -er # release mode, skip emsdk
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
@@ -57,17 +60,20 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install SDL2 Packages
|
||||
run: sudo apt-get install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev libsdl2-gfx-dev
|
||||
|
||||
- name: Setup Rust Toolchain (Linux)
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
target: ${{ env.TARGET }}
|
||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Vcpkg
|
||||
run: |
|
||||
cargo install cargo-vcpkg
|
||||
cargo vcpkg build
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release
|
||||
|
||||
@@ -105,17 +111,20 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install SDL2 Packages
|
||||
run: brew install sdl2 sdl2_image sdl2_mixer sdl2_ttf sdl2_gfx
|
||||
|
||||
- name: Setup Rust Toolchain (MacOS)
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
target: ${{ env.TARGET }}
|
||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Vcpkg
|
||||
run: |
|
||||
cargo install cargo-vcpkg
|
||||
cargo vcpkg build
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release
|
||||
|
||||
@@ -157,45 +166,20 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download SDL2 Libraries
|
||||
run: |
|
||||
curl -L "https://github.com/libsdl-org/SDL/releases/download/release-${{ env.SDL2 }}/SDL2-devel-${{ env.SDL2 }}-VC.zip" -o "sdl2_devel.zip"
|
||||
curl -L "https://github.com/libsdl-org/SDL_mixer/releases/download/release-${{ env.SDL2_MIXER }}/SDL2_mixer-devel-${{ env.SDL2_MIXER }}-VC.zip" -o "sdl2_mixer_devel.zip"
|
||||
curl -L "https://github.com/libsdl-org/SDL_ttf/releases/download/release-${{ env.SDL2_TTF }}/SDL2_ttf-devel-${{ env.SDL2_TTF }}-VC.zip" -o "sdl2_ttf_devel.zip"
|
||||
curl -L "https://github.com/libsdl-org/SDL_image/releases/download/release-${{ env.SDL2_IMAGE }}/SDL2_image-devel-${{ env.SDL2_IMAGE }}-VC.zip" -o "sdl2_image_devel.zip"
|
||||
|
||||
- name: Extract SDL2 DLLs
|
||||
run: |
|
||||
7z x ./sdl2_devel.zip -o"./tmp/"
|
||||
mv ./tmp/SDL2-${{ env.SDL2 }}/lib/x64/SDL2.dll ./
|
||||
mv ./tmp/SDL2-${{ env.SDL2 }}/lib/x64/SDL2.lib ./
|
||||
|
||||
7z x ./sdl2_mixer_devel.zip -o"./tmp/"
|
||||
mv ./tmp/SDL2_mixer-${{ env.SDL2_MIXER }}/lib/x64/SDL2_mixer.dll ./
|
||||
mv ./tmp/SDL2_mixer-${{ env.SDL2_MIXER }}/lib/x64/SDL2_mixer.lib ./
|
||||
|
||||
7z x ./sdl2_ttf_devel.zip -o"./tmp/"
|
||||
mv ./tmp/SDL2_ttf-${{ env.SDL2_TTF }}/lib/x64/SDL2_ttf.dll ./
|
||||
mv ./tmp/SDL2_ttf-${{ env.SDL2_TTF }}/lib/x64/SDL2_ttf.lib ./
|
||||
|
||||
7z x ./sdl2_image_devel.zip -o"./tmp/"
|
||||
mv ./tmp/SDL2_image-${{ env.SDL2_IMAGE }}/lib/x64/SDL2_image.dll ./
|
||||
mv ./tmp/SDL2_image-${{ env.SDL2_IMAGE }}/lib/x64/SDL2_image.lib ./
|
||||
|
||||
- name: Install SDL2_gfx
|
||||
run: |
|
||||
C:\vcpkg\vcpkg.exe install sdl2-gfx:x64-windows-release
|
||||
cp C:\vcpkg\packages\sdl2-gfx_x64-windows-release\bin\SDL2_gfx.dll ./
|
||||
cp C:\vcpkg\packages\sdl2-gfx_x64-windows-release\lib\SDL2_gfx.lib ./
|
||||
|
||||
- name: Setup Rust (Windows)
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
targets: ${{ env.TARGET }}
|
||||
target: ${{ env.TARGET }}
|
||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Vcpkg
|
||||
run: |
|
||||
cargo install cargo-vcpkg
|
||||
cargo vcpkg build
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release
|
||||
|
||||
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -123,16 +123,6 @@ impl<'a> AnimatedAtlasTexture<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pause(&mut self) {
|
||||
self.paused = true;
|
||||
}
|
||||
pub fn resume(&mut self) {
|
||||
self.paused = false;
|
||||
}
|
||||
pub fn is_paused(&self) -> bool {
|
||||
self.paused
|
||||
}
|
||||
|
||||
pub fn set_color_modulation(&mut self, r: u8, g: u8, b: u8) {
|
||||
self.atlas.set_color_modulation(r, g, b);
|
||||
}
|
||||
|
||||
31
src/debug.rs
31
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<Window>, _map: &Map, cell: (u32, u32), color: Color) {
|
||||
pub fn draw_cell(canvas: &mut Canvas<Window>, _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<Window>, map: &Map, pacman_cell: (u32, u32)) {
|
||||
pub fn draw_debug_grid(canvas: &mut Canvas<Window>, 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<Window>, map: &Map, next_cell: (u32, u32)) {
|
||||
pub fn draw_next_cell(canvas: &mut Canvas<Window>, map: &Map, next_cell: UVec2) {
|
||||
Self::draw_cell(canvas, map, next_cell, Color::YELLOW);
|
||||
}
|
||||
|
||||
pub fn draw_valid_positions(canvas: &mut Canvas<Window>, 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<Window>, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<AtlasTexture<'a>>) -> Self {
|
||||
pub fn new(kind: EdibleKind, cell_position: UVec2, sprite: Rc<AtlasTexture<'a>>) -> 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<Window>) {
|
||||
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
|
||||
_ => {}
|
||||
|
||||
@@ -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<Direction>) -> (i32, i32);
|
||||
fn next_cell(&self, direction: Option<Direction>) -> IVec2;
|
||||
fn is_wall_ahead(&self, direction: Option<Direction>) -> 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<Direction>) -> (i32, i32) {
|
||||
fn next_cell(&self, direction: Option<Direction>) -> 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<Direction>) -> 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 {
|
||||
|
||||
15
src/game.rs
15
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 = <Pacman as crate::entity::Moving>::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());
|
||||
|
||||
65
src/ghost.rs
65
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<RefCell<Map>>,
|
||||
@@ -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<UVec2>, 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<Direction>) -> (i32, i32) {
|
||||
fn next_cell(&self, direction: Option<Direction>) -> IVec2 {
|
||||
self.base.next_cell(direction)
|
||||
}
|
||||
fn is_wall_ahead(&self, direction: Option<Direction>) -> 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<sdl2::video::Window>) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<RefCell<Map>>,
|
||||
@@ -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<Direction>) -> (i32, i32) {
|
||||
fn next_cell(&self, direction: Option<Direction>) -> IVec2 {
|
||||
self.ghost.next_cell(direction)
|
||||
}
|
||||
fn is_wall_ahead(&self, direction: Option<Direction>) -> bool {
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
80
src/map.rs
80
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<SignedPosition> 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<MapTile> {
|
||||
let x = cell.0 as usize;
|
||||
let y = cell.1 as usize;
|
||||
pub fn get_tile(&self, cell: IVec2) -> Option<MapTile> {
|
||||
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<Position> {
|
||||
pub fn get_valid_playable_positions(&mut self) -> &Vec<UVec2> {
|
||||
use MapTile::*;
|
||||
static CACHE: OnceCell<Vec<Position>> = OnceCell::new();
|
||||
static CACHE: OnceCell<Vec<UVec2>> = 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<Position> = visited.into_iter().collect();
|
||||
result.sort_unstable();
|
||||
let mut result: Vec<UVec2> = visited.into_iter().collect();
|
||||
result.sort_unstable_by_key(|v| (v.x, v.y));
|
||||
CACHE.get_or_init(|| result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Direction>) -> (i32, i32) {
|
||||
fn next_cell(&self, direction: Option<Direction>) -> IVec2 {
|
||||
self.base.next_cell(direction)
|
||||
}
|
||||
fn is_wall_ahead(&self, direction: Option<Direction>) -> 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<RefCell<Map>>) -> Pacman<'a> {
|
||||
pub fn new<'a>(starting_position: UVec2, atlas: Texture<'a>, map: Rc<RefCell<Map>>) -> 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 {
|
||||
<Pacman as Moving>::update_cell_position(self);
|
||||
if !<Pacman as Moving>::handle_tunnel(self) {
|
||||
@@ -110,7 +112,7 @@ impl Pacman<'_> {
|
||||
}
|
||||
if !self.stopped && self.base.modulation.next() {
|
||||
<Pacman as Moving>::move_forward(self);
|
||||
if self.internal_position_even() == (0, 0) {
|
||||
if self.internal_position_even() == UVec2::ZERO {
|
||||
<Pacman as Moving>::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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user