mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-06 13:15:47 -06:00
docs: minor documentation commentsa cross project
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
//! This module provides a simple animation system for textures.
|
||||
use sdl2::{
|
||||
rect::Rect,
|
||||
render::{Canvas, Texture},
|
||||
@@ -6,18 +7,37 @@ use sdl2::{
|
||||
|
||||
use crate::direction::Direction;
|
||||
|
||||
/// An animated texture, which is a texture that is rendered as a series of
|
||||
/// frames.
|
||||
pub struct AnimatedTexture<'a> {
|
||||
raw_texture: Texture<'a>,
|
||||
/// The current tick of the animation.
|
||||
ticker: u32,
|
||||
/// Whether the animation is currently playing in reverse.
|
||||
reversed: bool,
|
||||
/// The offset of the texture, in pixels.
|
||||
offset: (i32, i32),
|
||||
/// The number of ticks per frame.
|
||||
ticks_per_frame: u32,
|
||||
/// The number of frames in the animation.
|
||||
frame_count: u32,
|
||||
/// The width of each frame, in pixels.
|
||||
frame_width: u32,
|
||||
/// The height of each frame, in pixels.
|
||||
frame_height: u32,
|
||||
}
|
||||
|
||||
impl<'a> AnimatedTexture<'a> {
|
||||
/// Creates a new `AnimatedTexture`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `texture` - The texture to animate.
|
||||
/// * `ticks_per_frame` - The number of ticks to display each frame for.
|
||||
/// * `frame_count` - The number of frames in the animation.
|
||||
/// * `frame_width` - The width of each frame.
|
||||
/// * `frame_height` - The height of each frame.
|
||||
/// * `offset` - The offset of the texture, in pixels.
|
||||
pub fn new(
|
||||
texture: Texture<'a>,
|
||||
ticks_per_frame: u32,
|
||||
@@ -38,12 +58,14 @@ impl<'a> AnimatedTexture<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// Get the current frame number
|
||||
/// Returns the current frame number.
|
||||
fn current_frame(&self) -> u32 {
|
||||
self.ticker / self.ticks_per_frame
|
||||
}
|
||||
|
||||
// Move to the next frame. If we are at the end of the animation, reverse the direction
|
||||
/// Advances the animation by one tick.
|
||||
///
|
||||
/// The animation will play forwards, then backwards, then forwards, and so on.
|
||||
pub fn tick(&mut self) {
|
||||
if self.reversed {
|
||||
self.ticker -= 1;
|
||||
@@ -60,7 +82,7 @@ impl<'a> AnimatedTexture<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the frame rect (portion of the texture to render) for the given frame.
|
||||
/// Calculates the source rectangle for the given frame.
|
||||
fn get_frame_rect(&self, frame: u32) -> Rect {
|
||||
if frame >= self.frame_count {
|
||||
panic!("Frame {} is out of bounds for this texture", frame);
|
||||
@@ -74,6 +96,13 @@ impl<'a> AnimatedTexture<'a> {
|
||||
)
|
||||
}
|
||||
|
||||
/// Renders the animation to the canvas.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `canvas` - The canvas to render to.
|
||||
/// * `position` - The position to render the animation at.
|
||||
/// * `direction` - The direction the animation is facing.
|
||||
pub fn render(
|
||||
&mut self,
|
||||
canvas: &mut Canvas<Window>,
|
||||
@@ -84,7 +113,15 @@ impl<'a> AnimatedTexture<'a> {
|
||||
self.tick();
|
||||
}
|
||||
|
||||
// Functions like render, but only ticks the animation until the given frame is reached.
|
||||
/// Renders the animation to the canvas, but only ticks the animation until
|
||||
/// the given frame is reached.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `canvas` - The canvas to render to.
|
||||
/// * `position` - The position to render the animation at.
|
||||
/// * `direction` - The direction the animation is facing.
|
||||
/// * `frame` - The frame to render until.
|
||||
pub fn render_until(
|
||||
&mut self,
|
||||
canvas: &mut Canvas<Window>,
|
||||
@@ -92,7 +129,9 @@ impl<'a> AnimatedTexture<'a> {
|
||||
direction: Direction,
|
||||
frame: u32,
|
||||
) {
|
||||
// TODO: If the frame we're targeting is in the opposite direction (due to self.reverse), we should pre-emptively reverse.
|
||||
// TODO: If the frame we're targeting is in the opposite direction (due
|
||||
// to self.reverse), we should pre-emptively reverse. This would require
|
||||
// a more complex ticking mechanism.
|
||||
let current = self.current_frame();
|
||||
self.render_static(canvas, position, direction, Some(current));
|
||||
|
||||
@@ -101,7 +140,14 @@ impl<'a> AnimatedTexture<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// Renders a specific frame of the animation. Defaults to the current frame.
|
||||
/// Renders a specific frame of the animation.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `canvas` - The canvas to render to.
|
||||
/// * `position` - The position to render the animation at.
|
||||
/// * `direction` - The direction the animation is facing.
|
||||
/// * `frame` - The frame to render. If `None`, the current frame is used.
|
||||
pub fn render_static(
|
||||
&mut self,
|
||||
canvas: &mut Canvas<Window>,
|
||||
|
||||
11
src/audio.rs
11
src/audio.rs
@@ -1,16 +1,21 @@
|
||||
//! This module handles the audio playback for the game.
|
||||
use sdl2::{
|
||||
mixer::{self, Chunk, InitFlag, LoaderRWops, DEFAULT_FORMAT},
|
||||
rwops::RWops,
|
||||
};
|
||||
|
||||
// Embed sound files directly into the executable
|
||||
const SOUND_1_DATA: &[u8] = include_bytes!("../assets/wav/1.ogg");
|
||||
const SOUND_2_DATA: &[u8] = include_bytes!("../assets/wav/2.ogg");
|
||||
const SOUND_3_DATA: &[u8] = include_bytes!("../assets/wav/3.ogg");
|
||||
const SOUND_4_DATA: &[u8] = include_bytes!("../assets/wav/4.ogg");
|
||||
|
||||
/// An array of all the sound effect data.
|
||||
const SOUND_DATA: [&[u8]; 4] = [SOUND_1_DATA, SOUND_2_DATA, SOUND_3_DATA, SOUND_4_DATA];
|
||||
|
||||
/// The audio system for the game.
|
||||
///
|
||||
/// This struct is responsible for initializing the audio device, loading sounds,
|
||||
/// and playing them.
|
||||
pub struct Audio {
|
||||
_mixer_context: mixer::Sdl2MixerContext,
|
||||
sounds: Vec<Chunk>,
|
||||
@@ -18,6 +23,7 @@ pub struct Audio {
|
||||
}
|
||||
|
||||
impl Audio {
|
||||
/// Creates a new `Audio` instance.
|
||||
pub fn new() -> Self {
|
||||
let frequency = 44100;
|
||||
let format = DEFAULT_FORMAT;
|
||||
@@ -53,6 +59,9 @@ impl Audio {
|
||||
}
|
||||
}
|
||||
|
||||
/// Plays the "eat" sound effect.
|
||||
///
|
||||
/// This function also logs the time since the last sound effect was played.
|
||||
pub fn eat(&mut self) {
|
||||
if let Some(chunk) = self.sounds.get(self.next_sound_index) {
|
||||
match mixer::Channel(0).play(chunk, 0) {
|
||||
|
||||
@@ -1,21 +1,40 @@
|
||||
//! This module contains all the constants used in the game.
|
||||
|
||||
/// The width of the game board, in cells.
|
||||
pub const BOARD_WIDTH: u32 = 28;
|
||||
pub const BOARD_HEIGHT: u32 = 31; // Adjusted to fit map texture?
|
||||
/// The height of the game board, in cells.
|
||||
pub const BOARD_HEIGHT: u32 = 31;
|
||||
/// The size of each cell, in pixels.
|
||||
pub const CELL_SIZE: u32 = 24;
|
||||
|
||||
pub const BOARD_OFFSET: (u32, u32) = (0, 3); // Relative cell offset for where map text / grid starts
|
||||
/// The offset of the game board from the top-left corner of the window, in
|
||||
/// cells.
|
||||
pub const BOARD_OFFSET: (u32, u32) = (0, 3);
|
||||
|
||||
/// The width of the window, in pixels.
|
||||
pub const WINDOW_WIDTH: u32 = CELL_SIZE * BOARD_WIDTH;
|
||||
pub const WINDOW_HEIGHT: u32 = CELL_SIZE * (BOARD_HEIGHT + 6); // Map texture is 6 cells taller (3 above, 3 below) than the grid
|
||||
/// The height of the window, in pixels.
|
||||
///
|
||||
/// The map texture is 6 cells taller than the grid (3 above, 3 below), so we
|
||||
/// add 6 to the board height to get the window height.
|
||||
pub const WINDOW_HEIGHT: u32 = CELL_SIZE * (BOARD_HEIGHT + 6);
|
||||
|
||||
/// An enum representing the different types of tiles on the map.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum MapTile {
|
||||
/// An empty tile.
|
||||
Empty,
|
||||
/// A wall tile.
|
||||
Wall,
|
||||
/// A regular pellet.
|
||||
Pellet,
|
||||
/// A power pellet.
|
||||
PowerPellet,
|
||||
/// A starting position for an entity.
|
||||
StartingPosition(u8),
|
||||
}
|
||||
|
||||
/// The raw layout of the game board, as a 2D array of characters.
|
||||
pub const RAW_BOARD: [&str; BOARD_HEIGHT as usize] = [
|
||||
"############################",
|
||||
"#............##............#",
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
//! This module defines the `Entity` trait, which is implemented by all game
|
||||
//! objects that can be moved and rendered.
|
||||
|
||||
/// A trait for game objects that can be moved and rendered.
|
||||
pub trait Entity {
|
||||
// Returns true if the entity is colliding with the other entity
|
||||
/// Returns true if the entity is colliding with the other entity.
|
||||
fn is_colliding(&self, other: &dyn Entity) -> bool;
|
||||
// Returns the absolute position of the entity
|
||||
/// Returns the absolute position of the entity, in pixels.
|
||||
fn position(&self) -> (i32, i32);
|
||||
// Returns the cell position of the entity (XY position within the grid)
|
||||
/// Returns the cell position of the entity, in grid coordinates.
|
||||
fn cell_position(&self) -> (u32, u32);
|
||||
/// Returns the position of the entity within its current cell, in pixels.
|
||||
fn internal_position(&self) -> (u32, u32);
|
||||
// Tick the entity (move it, perform collision checks, etc)
|
||||
/// Ticks the entity, which updates its state and position.
|
||||
fn tick(&mut self);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! This module contains helper functions that are used throughout the game.
|
||||
|
||||
/// Checks if two grid positions are adjacent to each other
|
||||
///
|
||||
/// # Arguments
|
||||
|
||||
22
src/main.rs
22
src/main.rs
@@ -22,6 +22,11 @@ use winapi::{
|
||||
},
|
||||
};
|
||||
|
||||
/// Attaches the process to the parent console on Windows.
|
||||
///
|
||||
/// This allows the application to print to the console when run from a terminal,
|
||||
/// which is useful for debugging purposes. If the application is not run from a
|
||||
/// terminal, this function does nothing.
|
||||
#[cfg(windows)]
|
||||
unsafe fn attach_console() {
|
||||
if GetConsoleWindow() != std::ptr::null_mut() as HWND {
|
||||
@@ -58,7 +63,12 @@ mod map;
|
||||
mod modulation;
|
||||
mod pacman;
|
||||
|
||||
/// The main entry point of the application.
|
||||
///
|
||||
/// This function initializes SDL, the window, the game state, and then enters
|
||||
/// the main game loop.
|
||||
pub fn main() {
|
||||
// Attaches the console on Windows for debugging purposes.
|
||||
#[cfg(windows)]
|
||||
unsafe {
|
||||
attach_console();
|
||||
@@ -109,13 +119,16 @@ pub fn main() {
|
||||
game.draw();
|
||||
game.tick();
|
||||
|
||||
// The target time for each frame of the game loop (60 FPS).
|
||||
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;
|
||||
// Whether the window is currently shown.
|
||||
let mut shown = false;
|
||||
|
||||
event!(
|
||||
@@ -126,7 +139,9 @@ pub fn main() {
|
||||
let mut main_loop = || {
|
||||
let start = Instant::now();
|
||||
|
||||
// TODO: Fix key repeat delay issues by using VecDeque for instant key repeat
|
||||
// TODO: Fix key repeat delay issues by using a queue for keyboard events.
|
||||
// This would allow for instant key repeat without being affected by the
|
||||
// main loop's tick rate.
|
||||
for event in event_pump.poll_iter() {
|
||||
match event {
|
||||
Event::Window { win_event, .. } => match win_event {
|
||||
@@ -167,9 +182,9 @@ pub fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Proper pausing implementation that does not interfere with statistic gathering
|
||||
// TODO: Implement a proper pausing mechanism that does not interfere with
|
||||
// statistic gathering and other background tasks.
|
||||
if !paused {
|
||||
// game.audio_demo_tick();
|
||||
game.tick();
|
||||
game.draw();
|
||||
}
|
||||
@@ -197,6 +212,7 @@ 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 {
|
||||
|
||||
38
src/map.rs
38
src/map.rs
@@ -1,12 +1,24 @@
|
||||
use crate::constants::MapTile;
|
||||
use crate::constants::{BOARD_HEIGHT, BOARD_WIDTH, RAW_BOARD};
|
||||
//! This module defines the game map and provides functions for interacting with it.
|
||||
use crate::constants::{MapTile, BOARD_OFFSET, CELL_SIZE};
|
||||
use crate::constants::{BOARD_HEIGHT, BOARD_WIDTH};
|
||||
|
||||
/// The game map.
|
||||
///
|
||||
/// The map is represented as a 2D array of `MapTile`s. It also stores a copy of
|
||||
/// the original map, which can be used to reset the map to its initial state.
|
||||
pub struct Map {
|
||||
/// The current state of the map.
|
||||
current: [[MapTile; BOARD_HEIGHT as usize]; BOARD_WIDTH as usize],
|
||||
/// The default state of the map.
|
||||
default: [[MapTile; BOARD_HEIGHT as usize]; BOARD_WIDTH as usize],
|
||||
}
|
||||
|
||||
impl Map {
|
||||
/// Creates a new `Map` instance from a raw board layout.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `raw_board` - A 2D array of characters representing the board layout.
|
||||
pub fn new(raw_board: [&str; BOARD_HEIGHT as usize]) -> Map {
|
||||
let mut map = [[MapTile::Empty; BOARD_HEIGHT as usize]; BOARD_WIDTH as usize];
|
||||
|
||||
@@ -46,6 +58,7 @@ impl Map {
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets the map to its original state.
|
||||
pub fn reset(&mut self) {
|
||||
// Restore the map to its original state
|
||||
for x in 0..BOARD_WIDTH as usize {
|
||||
@@ -55,6 +68,11 @@ impl Map {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the tile at the given cell coordinates.
|
||||
///
|
||||
/// # 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;
|
||||
@@ -66,6 +84,12 @@ impl Map {
|
||||
Some(self.current[x][y])
|
||||
}
|
||||
|
||||
/// Sets the tile at the given cell coordinates.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `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;
|
||||
@@ -78,7 +102,15 @@ impl Map {
|
||||
true
|
||||
}
|
||||
|
||||
/// Converts cell coordinates to pixel coordinates.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cell` - The cell coordinates, in grid coordinates.
|
||||
pub fn cell_to_pixel(cell: (u32, u32)) -> (i32, i32) {
|
||||
((cell.0 as i32) * 24, ((cell.1 + 3) as i32) * 24)
|
||||
(
|
||||
(cell.0 * CELL_SIZE) as i32,
|
||||
((cell.1 + BOARD_OFFSET.1) * CELL_SIZE) as i32,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! This module provides a tick modulator, which can be used to slow down
|
||||
//! operations by a percentage.
|
||||
/// A tick modulator allows you to slow down operations by a percentage.
|
||||
///
|
||||
/// Unfortunately, switching to floating point numbers for entities can induce floating point errors, slow down calculations
|
||||
@@ -12,17 +14,25 @@
|
||||
///
|
||||
/// For example, if we want to slow down the speed by 10%, we would need to skip every 10th tick.
|
||||
pub trait TickModulator {
|
||||
/// Creates a new tick modulator.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `percent` - The percentage to slow down by, from 0.0 to 1.0.
|
||||
fn new(percent: f32) -> Self;
|
||||
/// Returns whether or not the operation should be performed on this tick.
|
||||
fn next(&mut self) -> bool;
|
||||
}
|
||||
|
||||
/// A simple tick modulator that skips every Nth tick.
|
||||
pub struct SimpleTickModulator {
|
||||
tick_count: u32,
|
||||
ticks_left: u32,
|
||||
}
|
||||
|
||||
// TODO: Add tests
|
||||
// TODO: Look into average precision, binary code modulation strategy
|
||||
// TODO: Add tests for the tick modulator to ensure that it is working correctly.
|
||||
// TODO: Look into average precision and binary code modulation strategies to see
|
||||
// if they would be a better fit for this use case.
|
||||
impl TickModulator for SimpleTickModulator {
|
||||
fn new(percent: f32) -> Self {
|
||||
let ticks_required: u32 = (1f32 / (1f32 - percent)).round() as u32;
|
||||
@@ -34,15 +44,12 @@ impl TickModulator for SimpleTickModulator {
|
||||
}
|
||||
|
||||
fn next(&mut self) -> bool {
|
||||
self.ticks_left -= 1;
|
||||
|
||||
// Return whether or not we should skip this tick
|
||||
if self.ticks_left == 0 {
|
||||
// We've reached the tick to skip, reset the counter
|
||||
self.ticks_left = self.tick_count;
|
||||
false
|
||||
} else {
|
||||
true
|
||||
return false;
|
||||
}
|
||||
|
||||
self.ticks_left -= 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user