docs: minor documentation commentsa cross project

This commit is contained in:
2025-07-22 12:03:16 -05:00
parent f51a3ddeb0
commit f540dc5373
8 changed files with 165 additions and 29 deletions

View File

@@ -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>,

View File

@@ -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) {

View File

@@ -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] = [
"############################",
"#............##............#",

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -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,
)
}
}

View File

@@ -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
}
}