mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-08 04:07:52 -06:00
feat: flood-filled based playable position with cache, debug mode
This commit is contained in:
20
src/main.rs
20
src/main.rs
@@ -123,12 +123,7 @@ pub fn main() {
|
|||||||
|
|
||||||
// The target time for each frame of the game loop (60 FPS).
|
// The target time for each frame of the game loop (60 FPS).
|
||||||
let loop_time = Duration::from_secs(1) / 60;
|
let loop_time = Duration::from_secs(1) / 60;
|
||||||
let mut tick_no = 0u32;
|
|
||||||
|
|
||||||
// The start of a period of time over which we average the frame time.
|
|
||||||
let mut last_averaging_time = Instant::now();
|
|
||||||
// The total time spent sleeping during the current averaging period.
|
|
||||||
let mut sleep_time = Duration::ZERO;
|
|
||||||
let mut paused = false;
|
let mut paused = false;
|
||||||
// Whether the window is currently shown.
|
// Whether the window is currently shown.
|
||||||
let mut shown = false;
|
let mut shown = false;
|
||||||
@@ -203,7 +198,6 @@ pub fn main() {
|
|||||||
std::thread::sleep(time);
|
std::thread::sleep(time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sleep_time += time;
|
|
||||||
} else {
|
} else {
|
||||||
event!(
|
event!(
|
||||||
tracing::Level::WARN,
|
tracing::Level::WARN,
|
||||||
@@ -212,20 +206,6 @@ pub fn main() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
tick_no += 1;
|
|
||||||
|
|
||||||
// Caclulate and display performance statistics every 60 seconds.
|
|
||||||
const PERIOD: u32 = 60 * 60;
|
|
||||||
let tick_mod = tick_no % PERIOD;
|
|
||||||
if tick_mod % PERIOD == 0 {
|
|
||||||
let average_fps = PERIOD as f32 / last_averaging_time.elapsed().as_secs_f32();
|
|
||||||
let average_sleep = sleep_time / PERIOD;
|
|
||||||
let average_process = loop_time - average_sleep;
|
|
||||||
|
|
||||||
sleep_time = Duration::ZERO;
|
|
||||||
last_averaging_time = Instant::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
102
src/map.rs
102
src/map.rs
@@ -1,16 +1,42 @@
|
|||||||
//! This module defines the game map and provides functions for interacting with it.
|
//! This module defines the game map and provides functions for interacting with it.
|
||||||
|
use rand::seq::IteratorRandom;
|
||||||
|
|
||||||
use crate::constants::{MapTile, BOARD_OFFSET, CELL_SIZE};
|
use crate::constants::{MapTile, BOARD_OFFSET, CELL_SIZE};
|
||||||
use crate::constants::{BOARD_HEIGHT, BOARD_WIDTH};
|
use crate::constants::{BOARD_HEIGHT, BOARD_WIDTH};
|
||||||
|
use std::collections::{HashSet, VecDeque};
|
||||||
|
use std::ops::Add;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||||
pub struct Position(pub u32, pub u32);
|
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 Position {
|
impl Position {
|
||||||
pub fn as_i32(&self) -> (i32, i32) {
|
pub fn as_i32(&self) -> (i32, i32) {
|
||||||
(self.0 as i32, self.1 as i32)
|
(self.x as i32, self.y as i32)
|
||||||
}
|
}
|
||||||
pub fn wrapping_add(&self, dx: i32, dy: i32) -> Position {
|
pub fn wrapping_add(&self, dx: i32, dy: i32) -> Position {
|
||||||
Position((self.0 as i32 + dx) as u32, (self.1 as i32 + dy) as u32)
|
Position {
|
||||||
|
x: (self.x as i32 + dx) as u32,
|
||||||
|
y: (self.y as i32 + dy) as u32,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,4 +152,74 @@ impl Map {
|
|||||||
((cell.1 + BOARD_OFFSET.1) * CELL_SIZE) as i32,
|
((cell.1 + 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> {
|
||||||
|
use MapTile::*;
|
||||||
|
static mut CACHE: Option<Vec<Position>> = None;
|
||||||
|
// SAFETY: This is only mutated once, and only in this function.
|
||||||
|
unsafe {
|
||||||
|
if let Some(ref cached) = CACHE {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Find a random starting pellet
|
||||||
|
let mut pellet_positions = vec![];
|
||||||
|
for x in 0..BOARD_WIDTH as u32 {
|
||||||
|
for y in 0..BOARD_HEIGHT as u32 {
|
||||||
|
match self.current[x as usize][y as usize] {
|
||||||
|
Pellet | PowerPellet => pellet_positions.push(Position { x, y }),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut rng = rand::rng();
|
||||||
|
let &start = pellet_positions
|
||||||
|
.iter()
|
||||||
|
.choose(&mut rng)
|
||||||
|
.expect("No pellet found for flood fill");
|
||||||
|
// Flood fill
|
||||||
|
let mut visited = HashSet::new();
|
||||||
|
let mut queue = VecDeque::new();
|
||||||
|
|
||||||
|
queue.push_back(start);
|
||||||
|
while let Some(pos) = queue.pop_front() {
|
||||||
|
// Mark visited, skip if already visited
|
||||||
|
if !visited.insert(pos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the current tile is valid
|
||||||
|
match self.current[pos.x as usize][pos.y as usize] {
|
||||||
|
Empty | Pellet | PowerPellet => {
|
||||||
|
// Valid, continue flood
|
||||||
|
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;
|
||||||
|
if neighbor.x < BOARD_WIDTH as u32 && neighbor.y < BOARD_HEIGHT as u32 {
|
||||||
|
let neighbor_tile =
|
||||||
|
self.current[neighbor.x as usize][neighbor.y as usize];
|
||||||
|
if matches!(neighbor_tile, Empty | Pellet | PowerPellet) {
|
||||||
|
queue.push_back(neighbor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StartingPosition(_) | Wall | Tunnel => {
|
||||||
|
// Not valid, do not continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut result: Vec<Position> = visited.into_iter().collect();
|
||||||
|
result.sort_unstable();
|
||||||
|
unsafe {
|
||||||
|
CACHE = Some(result);
|
||||||
|
CACHE.as_ref().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user