mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-06 17:15:47 -06:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d18b414536 | |||
| c9bcf32381 | |||
| b45980c172 |
2
.github/workflows/build.yaml
vendored
2
.github/workflows/build.yaml
vendored
@@ -127,9 +127,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-pages-artifact@v3
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
|
||||||
with:
|
with:
|
||||||
path: "./dist/"
|
path: "./dist/"
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
- name: Deploy
|
- name: Deploy
|
||||||
|
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
|
||||||
uses: actions/deploy-pages@v4
|
uses: actions/deploy-pages@v4
|
||||||
|
|||||||
1
.github/workflows/test.yaml
vendored
1
.github/workflows/test.yaml
vendored
@@ -19,6 +19,7 @@ jobs:
|
|||||||
uses: dtolnay/rust-toolchain@master
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||||
|
components: clippy
|
||||||
|
|
||||||
- name: Rust Cache
|
- name: Rust Cache
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
//! Cross-platform asset loading abstraction.
|
//! Cross-platform asset loading abstraction.
|
||||||
//! On desktop, assets are embedded using include_bytes!; on Emscripten, assets are loaded from the filesystem.
|
//! On desktop, assets are embedded using include_bytes!; on Emscripten, assets are loaded from the filesystem.
|
||||||
|
|
||||||
@@ -5,7 +6,6 @@ use std::borrow::Cow;
|
|||||||
use std::io;
|
use std::io;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum AssetError {
|
pub enum AssetError {
|
||||||
#[error("IO error: {0}")]
|
#[error("IO error: {0}")]
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const SOUND_ASSETS: [Asset; 4] = [Asset::Wav1, Asset::Wav2, Asset::Wav3, Asset::
|
|||||||
///
|
///
|
||||||
/// This struct is responsible for initializing the audio device, loading sounds,
|
/// This struct is responsible for initializing the audio device, loading sounds,
|
||||||
/// and playing them.
|
/// and playing them.
|
||||||
|
#[allow(dead_code)]
|
||||||
pub struct Audio {
|
pub struct Audio {
|
||||||
_mixer_context: mixer::Sdl2MixerContext,
|
_mixer_context: mixer::Sdl2MixerContext,
|
||||||
sounds: Vec<Chunk>,
|
sounds: Vec<Chunk>,
|
||||||
@@ -18,6 +19,12 @@ pub struct Audio {
|
|||||||
muted: bool,
|
muted: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Audio {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Audio {
|
impl Audio {
|
||||||
/// Creates a new `Audio` instance.
|
/// Creates a new `Audio` instance.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
@@ -57,6 +64,7 @@ impl Audio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Plays the "eat" sound effect.
|
/// Plays the "eat" sound effect.
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn eat(&mut self) {
|
pub fn eat(&mut self) {
|
||||||
if let Some(chunk) = self.sounds.get(self.next_sound_index) {
|
if let Some(chunk) = self.sounds.get(self.next_sound_index) {
|
||||||
match mixer::Channel(0).play(chunk, 0) {
|
match mixer::Channel(0).play(chunk, 0) {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ impl Direction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_ivec2(&self) -> IVec2 {
|
pub fn as_ivec2(&self) -> IVec2 {
|
||||||
(*self).into()
|
(*self).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ pub struct Node {
|
|||||||
/// Each field contains an optional edge leading in that direction.
|
/// Each field contains an optional edge leading in that direction.
|
||||||
/// This structure is used to represent the adjacency list for each node,
|
/// This structure is used to represent the adjacency list for each node,
|
||||||
/// providing O(1) access to edges in any cardinal direction.
|
/// providing O(1) access to edges in any cardinal direction.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Intersection {
|
pub struct Intersection {
|
||||||
/// Edge leading upward from this node, if it exists.
|
/// Edge leading upward from this node, if it exists.
|
||||||
pub up: Option<Edge>,
|
pub up: Option<Edge>,
|
||||||
@@ -40,17 +40,6 @@ pub struct Intersection {
|
|||||||
pub right: Option<Edge>,
|
pub right: Option<Edge>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Intersection {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
up: None,
|
|
||||||
down: None,
|
|
||||||
left: None,
|
|
||||||
right: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Intersection {
|
impl Intersection {
|
||||||
/// Returns an iterator over all edges from this intersection.
|
/// Returns an iterator over all edges from this intersection.
|
||||||
///
|
///
|
||||||
@@ -253,6 +242,7 @@ pub enum Position {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
impl Position {
|
impl Position {
|
||||||
/// Returns `true` if the position is exactly at a node.
|
/// Returns `true` if the position is exactly at a node.
|
||||||
pub fn is_at_node(&self) -> bool {
|
pub fn is_at_node(&self) -> bool {
|
||||||
@@ -260,6 +250,7 @@ impl Position {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `NodeId` of the current or most recently departed node.
|
/// Returns the `NodeId` of the current or most recently departed node.
|
||||||
|
#[allow(clippy::wrong_self_convention)]
|
||||||
pub fn from_node_id(&self) -> NodeId {
|
pub fn from_node_id(&self) -> NodeId {
|
||||||
match self {
|
match self {
|
||||||
Position::AtNode(id) => *id,
|
Position::AtNode(id) => *id,
|
||||||
@@ -268,6 +259,7 @@ impl Position {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `NodeId` of the destination node, if currently on an edge.
|
/// Returns the `NodeId` of the destination node, if currently on an edge.
|
||||||
|
#[allow(clippy::wrong_self_convention)]
|
||||||
pub fn to_node_id(&self) -> Option<NodeId> {
|
pub fn to_node_id(&self) -> Option<NodeId> {
|
||||||
match self {
|
match self {
|
||||||
Position::AtNode(_) => None,
|
Position::AtNode(_) => None,
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ impl Pacman {
|
|||||||
Direction::Right => "pacman/right",
|
Direction::Right => "pacman/right",
|
||||||
};
|
};
|
||||||
let moving_tiles = vec![
|
let moving_tiles = vec![
|
||||||
SpriteAtlas::get_tile(&atlas, &format!("{}_a.png", moving_prefix)).unwrap(),
|
SpriteAtlas::get_tile(atlas, &format!("{moving_prefix}_a.png")).unwrap(),
|
||||||
SpriteAtlas::get_tile(&atlas, &format!("{}_b.png", moving_prefix)).unwrap(),
|
SpriteAtlas::get_tile(atlas, &format!("{moving_prefix}_b.png")).unwrap(),
|
||||||
SpriteAtlas::get_tile(&atlas, "pacman/full.png").unwrap(),
|
SpriteAtlas::get_tile(atlas, "pacman/full.png").unwrap(),
|
||||||
];
|
];
|
||||||
|
|
||||||
let stopped_tiles = vec![SpriteAtlas::get_tile(&atlas, &format!("{}_b.png", moving_prefix)).unwrap()];
|
let stopped_tiles = vec![SpriteAtlas::get_tile(atlas, &format!("{moving_prefix}_b.png")).unwrap()];
|
||||||
|
|
||||||
textures.insert(direction, AnimatedTexture::new(moving_tiles, 0.08));
|
textures.insert(direction, AnimatedTexture::new(moving_tiles, 0.08));
|
||||||
stopped_textures.insert(direction, AnimatedTexture::new(stopped_tiles, 0.1));
|
stopped_textures.insert(direction, AnimatedTexture::new(stopped_tiles, 0.1));
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ pub struct Game {
|
|||||||
atlas: SpriteAtlas,
|
atlas: SpriteAtlas,
|
||||||
map_texture: AtlasTile,
|
map_texture: AtlasTile,
|
||||||
text_texture: TextTexture,
|
text_texture: TextTexture,
|
||||||
debug_text_texture: TextTexture,
|
|
||||||
|
|
||||||
// Audio
|
// Audio
|
||||||
pub audio: Audio,
|
pub audio: Audio,
|
||||||
@@ -71,9 +70,9 @@ impl Game {
|
|||||||
map_texture.color = Some(Color::RGB(0x20, 0x20, 0xf9));
|
map_texture.color = Some(Color::RGB(0x20, 0x20, 0xf9));
|
||||||
|
|
||||||
let text_texture = TextTexture::new(1.0);
|
let text_texture = TextTexture::new(1.0);
|
||||||
let debug_text_texture = TextTexture::new(0.5);
|
|
||||||
let audio = Audio::new();
|
let audio = Audio::new();
|
||||||
let pacman = Pacman::new(&map.graph, pacman_start_node, &atlas);
|
let pacman = Pacman::new(&map.graph, pacman_start_node, &atlas);
|
||||||
|
|
||||||
Game {
|
Game {
|
||||||
score: 0,
|
score: 0,
|
||||||
map,
|
map,
|
||||||
@@ -81,7 +80,6 @@ impl Game {
|
|||||||
debug_mode: false,
|
debug_mode: false,
|
||||||
map_texture,
|
map_texture,
|
||||||
text_texture,
|
text_texture,
|
||||||
debug_text_texture,
|
|
||||||
audio,
|
audio,
|
||||||
atlas,
|
atlas,
|
||||||
}
|
}
|
||||||
@@ -92,7 +90,6 @@ impl Game {
|
|||||||
|
|
||||||
if keycode == Keycode::M {
|
if keycode == Keycode::M {
|
||||||
self.audio.set_mute(!self.audio.is_muted());
|
self.audio.set_mute(!self.audio.is_muted());
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +119,6 @@ impl Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn draw_hud<T: RenderTarget>(&mut self, canvas: &mut Canvas<T>) -> Result<()> {
|
fn draw_hud<T: RenderTarget>(&mut self, canvas: &mut Canvas<T>) -> Result<()> {
|
||||||
let score_text = self.score.to_string();
|
|
||||||
let lives = 3;
|
let lives = 3;
|
||||||
let score_text = format!("{:02}", self.score);
|
let score_text = format!("{:02}", self.score);
|
||||||
let x_offset = 4;
|
let x_offset = 4;
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ impl Map {
|
|||||||
// Iterate over the queue, adding nodes to the graph and connecting them to their neighbors
|
// Iterate over the queue, adding nodes to the graph and connecting them to their neighbors
|
||||||
while let Some(source_position) = queue.pop_front() {
|
while let Some(source_position) = queue.pop_front() {
|
||||||
for &dir in DIRECTIONS.iter() {
|
for &dir in DIRECTIONS.iter() {
|
||||||
let new_position = source_position + dir.to_ivec2();
|
let new_position = source_position + dir.as_ivec2();
|
||||||
|
|
||||||
// Skip if the new position is out of bounds
|
// Skip if the new position is out of bounds
|
||||||
if new_position.x < 0
|
if new_position.x < 0
|
||||||
@@ -114,7 +114,7 @@ impl Map {
|
|||||||
// Connect the new node to the source node
|
// Connect the new node to the source node
|
||||||
let source_node_id = grid_to_node
|
let source_node_id = grid_to_node
|
||||||
.get(&source_position)
|
.get(&source_position)
|
||||||
.expect(&format!("Source node not found for {source_position}"));
|
.unwrap_or_else(|| panic!("Source node not found for {source_position}"));
|
||||||
|
|
||||||
// Connect the new node to the source node
|
// Connect the new node to the source node
|
||||||
graph
|
graph
|
||||||
@@ -129,7 +129,7 @@ impl Map {
|
|||||||
for dir in DIRECTIONS {
|
for dir in DIRECTIONS {
|
||||||
// If the node doesn't have an edge in this direction, look for a neighbor in that direction
|
// If the node doesn't have an edge in this direction, look for a neighbor in that direction
|
||||||
if graph.adjacency_list[node_id].get(dir).is_none() {
|
if graph.adjacency_list[node_id].get(dir).is_none() {
|
||||||
let neighbor = grid_pos + dir.to_ivec2();
|
let neighbor = grid_pos + dir.as_ivec2();
|
||||||
// If the neighbor exists, connect the node to it
|
// If the neighbor exists, connect the node to it
|
||||||
if let Some(&neighbor_id) = grid_to_node.get(&neighbor) {
|
if let Some(&neighbor_id) = grid_to_node.get(&neighbor) {
|
||||||
graph
|
graph
|
||||||
@@ -198,10 +198,10 @@ impl Map {
|
|||||||
let (house_entrance_node_id, house_entrance_node_position) = {
|
let (house_entrance_node_id, house_entrance_node_position) = {
|
||||||
// Translate the grid positions to the actual node ids
|
// Translate the grid positions to the actual node ids
|
||||||
let left_node = grid_to_node
|
let left_node = grid_to_node
|
||||||
.get(&(house_door[0].expect("First house door position not acquired") + Direction::Left.to_ivec2()))
|
.get(&(house_door[0].expect("First house door position not acquired") + Direction::Left.as_ivec2()))
|
||||||
.expect("Left house door node not found");
|
.expect("Left house door node not found");
|
||||||
let right_node = grid_to_node
|
let right_node = grid_to_node
|
||||||
.get(&(house_door[1].expect("Second house door position not acquired") + Direction::Right.to_ivec2()))
|
.get(&(house_door[1].expect("Second house door position not acquired") + Direction::Right.as_ivec2()))
|
||||||
.expect("Right house door node not found");
|
.expect("Right house door node not found");
|
||||||
|
|
||||||
// Calculate the position of the house node
|
// Calculate the position of the house node
|
||||||
@@ -230,10 +230,10 @@ impl Map {
|
|||||||
// Place the nodes at, above, and below the center position
|
// Place the nodes at, above, and below the center position
|
||||||
let center_node_id = graph.add_node(Node { position: center_pos });
|
let center_node_id = graph.add_node(Node { position: center_pos });
|
||||||
let top_node_id = graph.add_node(Node {
|
let top_node_id = graph.add_node(Node {
|
||||||
position: center_pos + (Direction::Up.to_ivec2() * (CELL_SIZE as i32 / 2)).as_vec2(),
|
position: center_pos + (Direction::Up.as_ivec2() * (CELL_SIZE as i32 / 2)).as_vec2(),
|
||||||
});
|
});
|
||||||
let bottom_node_id = graph.add_node(Node {
|
let bottom_node_id = graph.add_node(Node {
|
||||||
position: center_pos + (Direction::Down.to_ivec2() * (CELL_SIZE as i32 / 2)).as_vec2(),
|
position: center_pos + (Direction::Down.as_ivec2() * (CELL_SIZE as i32 / 2)).as_vec2(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Connect the center node to the top and bottom nodes
|
// Connect the center node to the top and bottom nodes
|
||||||
@@ -249,7 +249,7 @@ impl Map {
|
|||||||
|
|
||||||
// Calculate the position of the center line's center node
|
// Calculate the position of the center line's center node
|
||||||
let center_line_center_position =
|
let center_line_center_position =
|
||||||
house_entrance_node_position + (Direction::Down.to_ivec2() * (3 * CELL_SIZE as i32)).as_vec2();
|
house_entrance_node_position + (Direction::Down.as_ivec2() * (3 * CELL_SIZE as i32)).as_vec2();
|
||||||
|
|
||||||
// Create the center line
|
// Create the center line
|
||||||
let (center_center_node_id, center_top_node_id) = create_house_line(graph, center_line_center_position);
|
let (center_center_node_id, center_top_node_id) = create_house_line(graph, center_line_center_position);
|
||||||
@@ -262,13 +262,13 @@ impl Map {
|
|||||||
// Create the left line
|
// Create the left line
|
||||||
let (left_center_node_id, _) = create_house_line(
|
let (left_center_node_id, _) = create_house_line(
|
||||||
graph,
|
graph,
|
||||||
center_line_center_position + (Direction::Left.to_ivec2() * (CELL_SIZE as i32 * 2)).as_vec2(),
|
center_line_center_position + (Direction::Left.as_ivec2() * (CELL_SIZE as i32 * 2)).as_vec2(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create the right line
|
// Create the right line
|
||||||
let (right_center_node_id, _) = create_house_line(
|
let (right_center_node_id, _) = create_house_line(
|
||||||
graph,
|
graph,
|
||||||
center_line_center_position + (Direction::Right.to_ivec2() * (CELL_SIZE as i32 * 2)).as_vec2(),
|
center_line_center_position + (Direction::Right.as_ivec2() * (CELL_SIZE as i32 * 2)).as_vec2(),
|
||||||
);
|
);
|
||||||
|
|
||||||
debug!("Left center node id: {left_center_node_id}");
|
debug!("Left center node id: {left_center_node_id}");
|
||||||
@@ -300,7 +300,7 @@ impl Map {
|
|||||||
Direction::Left,
|
Direction::Left,
|
||||||
Node {
|
Node {
|
||||||
position: left_tunnel_entrance_node.position
|
position: left_tunnel_entrance_node.position
|
||||||
+ (Direction::Left.to_ivec2() * (CELL_SIZE as i32 * 2)).as_vec2(),
|
+ (Direction::Left.as_ivec2() * (CELL_SIZE as i32 * 2)).as_vec2(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.expect("Failed to connect left tunnel entrance to left tunnel hidden node")
|
.expect("Failed to connect left tunnel entrance to left tunnel hidden node")
|
||||||
@@ -319,7 +319,7 @@ impl Map {
|
|||||||
Direction::Right,
|
Direction::Right,
|
||||||
Node {
|
Node {
|
||||||
position: right_tunnel_entrance_node.position
|
position: right_tunnel_entrance_node.position
|
||||||
+ (Direction::Right.to_ivec2() * (CELL_SIZE as i32 * 2)).as_vec2(),
|
+ (Direction::Right.as_ivec2() * (CELL_SIZE as i32 * 2)).as_vec2(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.expect("Failed to connect right tunnel entrance to right tunnel hidden node")
|
.expect("Failed to connect right tunnel entrance to right tunnel hidden node")
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ impl AnimatedTexture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn render<T: RenderTarget>(&self, canvas: &mut Canvas<T>, atlas: &mut SpriteAtlas, dest: Rect) -> Result<()> {
|
pub fn render<T: RenderTarget>(&self, canvas: &mut Canvas<T>, atlas: &mut SpriteAtlas, dest: Rect) -> Result<()> {
|
||||||
let mut tile = self.current_tile().clone();
|
let mut tile = *self.current_tile();
|
||||||
tile.render(canvas, atlas, dest)
|
tile.render(canvas, atlas, dest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
use crate::texture::sprite::AtlasTile;
|
use crate::texture::sprite::AtlasTile;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|||||||
@@ -76,15 +76,30 @@ impl SpriteAtlas {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn set_color(&mut self, color: Color) {
|
pub fn set_color(&mut self, color: Color) {
|
||||||
self.default_color = Some(color);
|
self.default_color = Some(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn texture(&self) -> &Texture<'static> {
|
pub fn texture(&self) -> &Texture<'static> {
|
||||||
&self.texture
|
&self.texture
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts a `Texture` to a `Texture<'static>` using transmute.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function is unsafe because it uses `std::mem::transmute` to change the lifetime
|
||||||
|
/// of the texture from the original lifetime to `'static`. The caller must ensure that:
|
||||||
|
///
|
||||||
|
/// - The original `Texture` will live for the entire duration of the program
|
||||||
|
/// - No references to the original texture exist that could become invalid
|
||||||
|
/// - The texture is not dropped while still being used as a `'static` reference
|
||||||
|
///
|
||||||
|
/// This is typically used when you have a texture that you know will live for the entire
|
||||||
|
/// program duration and need to store it in a structure that requires a `'static` lifetime.
|
||||||
pub unsafe fn texture_to_static(texture: Texture) -> Texture<'static> {
|
pub unsafe fn texture_to_static(texture: Texture) -> Texture<'static> {
|
||||||
std::mem::transmute(texture)
|
std::mem::transmute(texture)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
//! This module provides text rendering using the texture atlas.
|
//! This module provides text rendering using the texture atlas.
|
||||||
//!
|
//!
|
||||||
//! The TextTexture system renders text from the atlas using character mapping.
|
//! The TextTexture system renders text from the atlas using character mapping.
|
||||||
|
|||||||
Reference in New Issue
Block a user