mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-08 04:07:52 -06:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1dc8aca373 | |||
| 02089a78da |
8
.github/workflows/build.yaml
vendored
8
.github/workflows/build.yaml
vendored
@@ -14,19 +14,19 @@ jobs:
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
artifact_name: pacman
|
||||
toolchain: 1.88.0
|
||||
toolchain: 1.86.0
|
||||
- os: macos-13
|
||||
target: x86_64-apple-darwin
|
||||
artifact_name: pacman
|
||||
toolchain: 1.88.0
|
||||
toolchain: 1.86.0
|
||||
- os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
artifact_name: pacman
|
||||
toolchain: 1.88.0
|
||||
toolchain: 1.86.0
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-gnu
|
||||
artifact_name: pacman.exe
|
||||
toolchain: 1.88.0
|
||||
toolchain: 1.86.0
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
2
.github/workflows/tests.yaml
vendored
2
.github/workflows/tests.yaml
vendored
@@ -4,7 +4,7 @@ on: ["push", "pull_request"]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUST_TOOLCHAIN: 1.88.0
|
||||
RUST_TOOLCHAIN: 1.86.0
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
||||
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "1.86.0"
|
||||
95
src/entity/item.rs
Normal file
95
src/entity/item.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
use crate::{
|
||||
constants,
|
||||
entity::graph::Graph,
|
||||
error::EntityError,
|
||||
texture::sprite::{Sprite, SpriteAtlas},
|
||||
};
|
||||
use sdl2::render::{Canvas, RenderTarget};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ItemType {
|
||||
Pellet,
|
||||
Energizer,
|
||||
#[allow(dead_code)]
|
||||
Fruit {
|
||||
kind: FruitKind,
|
||||
},
|
||||
}
|
||||
|
||||
impl ItemType {
|
||||
pub fn get_score(self) -> u32 {
|
||||
match self {
|
||||
ItemType::Pellet => 10,
|
||||
ItemType::Energizer => 50,
|
||||
ItemType::Fruit { kind } => kind.get_score(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
pub enum FruitKind {
|
||||
Apple,
|
||||
Strawberry,
|
||||
Orange,
|
||||
Melon,
|
||||
Bell,
|
||||
Key,
|
||||
Galaxian,
|
||||
}
|
||||
|
||||
impl FruitKind {
|
||||
pub fn get_score(self) -> u32 {
|
||||
match self {
|
||||
FruitKind::Apple => 100,
|
||||
FruitKind::Strawberry => 300,
|
||||
FruitKind::Orange => 500,
|
||||
FruitKind::Melon => 700,
|
||||
FruitKind::Bell => 1000,
|
||||
FruitKind::Key => 2000,
|
||||
FruitKind::Galaxian => 3000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Item {
|
||||
pub node_index: usize,
|
||||
pub item_type: ItemType,
|
||||
pub sprite: Sprite,
|
||||
pub collected: bool,
|
||||
}
|
||||
|
||||
impl Item {
|
||||
pub fn new(node_index: usize, item_type: ItemType, sprite: Sprite) -> Self {
|
||||
Self {
|
||||
node_index,
|
||||
item_type,
|
||||
sprite,
|
||||
collected: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_collected(&self) -> bool {
|
||||
self.collected
|
||||
}
|
||||
|
||||
pub fn collect(&mut self) {
|
||||
self.collected = true;
|
||||
}
|
||||
|
||||
pub fn get_score(&self) -> u32 {
|
||||
self.item_type.get_score()
|
||||
}
|
||||
|
||||
pub fn render<T: RenderTarget>(&self, canvas: &mut Canvas<T>, atlas: &mut SpriteAtlas, graph: &Graph) -> anyhow::Result<()> {
|
||||
if !self.collected {
|
||||
let node = graph
|
||||
.get_node(self.node_index)
|
||||
.ok_or(EntityError::NodeNotFound(self.node_index))?;
|
||||
let position = node.position + constants::BOARD_PIXEL_OFFSET.as_vec2();
|
||||
self.sprite.render(canvas, atlas, position)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod direction;
|
||||
pub mod ghost;
|
||||
pub mod graph;
|
||||
pub mod item;
|
||||
pub mod pacman;
|
||||
pub mod r#trait;
|
||||
pub mod traversal;
|
||||
|
||||
36
src/game.rs
36
src/game.rs
@@ -18,6 +18,7 @@ use crate::{
|
||||
constants::{CELL_SIZE, RAW_BOARD},
|
||||
entity::{
|
||||
ghost::{Ghost, GhostType},
|
||||
item::Item,
|
||||
pacman::Pacman,
|
||||
r#trait::Entity,
|
||||
},
|
||||
@@ -37,6 +38,7 @@ pub struct Game {
|
||||
pub map: Map,
|
||||
pub pacman: Pacman,
|
||||
pub ghosts: Vec<Ghost>,
|
||||
pub items: Vec<Item>,
|
||||
pub debug_mode: bool,
|
||||
|
||||
// Rendering resources
|
||||
@@ -87,6 +89,9 @@ impl Game {
|
||||
let audio = Audio::new();
|
||||
let pacman = Pacman::new(&map.graph, pacman_start_node, &atlas)?;
|
||||
|
||||
// Generate items (pellets and energizers)
|
||||
let items = map.generate_items(&atlas)?;
|
||||
|
||||
// Create ghosts at random positions
|
||||
let mut ghosts = Vec::new();
|
||||
let ghost_types = [GhostType::Blinky, GhostType::Pinky, GhostType::Inky, GhostType::Clyde];
|
||||
@@ -109,6 +114,7 @@ impl Game {
|
||||
map,
|
||||
pacman,
|
||||
ghosts,
|
||||
items,
|
||||
debug_mode: false,
|
||||
map_texture,
|
||||
text_texture,
|
||||
@@ -146,6 +152,9 @@ impl Game {
|
||||
|
||||
self.pacman = Pacman::new(&self.map.graph, pacman_start_node, &self.atlas)?;
|
||||
|
||||
// Reset items
|
||||
self.items = self.map.generate_items(&self.atlas)?;
|
||||
|
||||
// Randomize ghost positions
|
||||
let ghost_types = [GhostType::Blinky, GhostType::Pinky, GhostType::Inky, GhostType::Clyde];
|
||||
let mut rng = SmallRng::from_os_rng();
|
||||
@@ -165,6 +174,26 @@ impl Game {
|
||||
for ghost in &mut self.ghosts {
|
||||
ghost.tick(dt, &self.map.graph);
|
||||
}
|
||||
|
||||
// Check for item collisions
|
||||
self.check_item_collisions();
|
||||
}
|
||||
|
||||
fn check_item_collisions(&mut self) {
|
||||
let pacman_node = self.pacman.current_node_id();
|
||||
|
||||
for item in &mut self.items {
|
||||
if !item.is_collected() && item.node_index == pacman_node {
|
||||
item.collect();
|
||||
self.score += item.get_score();
|
||||
|
||||
// Handle energizer effects
|
||||
if matches!(item.item_type, crate::entity::item::ItemType::Energizer) {
|
||||
// TODO: Make ghosts frightened
|
||||
tracing::info!("Energizer collected! Ghosts should become frightened.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw<T: RenderTarget>(&mut self, canvas: &mut Canvas<T>, backbuffer: &mut Texture) -> GameResult<()> {
|
||||
@@ -174,6 +203,13 @@ impl Game {
|
||||
canvas.clear();
|
||||
self.map.render(canvas, &mut self.atlas, &mut self.map_texture);
|
||||
|
||||
// Render all items
|
||||
for item in &self.items {
|
||||
if let Err(e) = item.render(canvas, &mut self.atlas, &self.map.graph) {
|
||||
tracing::error!("Failed to render item: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Render all ghosts
|
||||
for ghost in &self.ghosts {
|
||||
if let Err(e) = ghost.render(canvas, &mut self.atlas, &self.map.graph) {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
//! Map construction and building functionality.
|
||||
|
||||
use crate::constants::{MapTile, BOARD_CELL_SIZE, CELL_SIZE};
|
||||
use crate::constants::{MapTile, BOARD_CELL_SIZE, CELL_SIZE, RAW_BOARD};
|
||||
use crate::entity::direction::Direction;
|
||||
use crate::entity::graph::{EdgePermissions, Graph, Node, NodeId};
|
||||
use crate::entity::item::{Item, ItemType};
|
||||
use crate::map::parser::MapTileParser;
|
||||
use crate::map::render::MapRenderer;
|
||||
use crate::texture::sprite::{AtlasTile, SpriteAtlas};
|
||||
use crate::texture::sprite::{AtlasTile, Sprite, SpriteAtlas};
|
||||
use glam::{IVec2, UVec2, Vec2};
|
||||
use sdl2::render::{Canvas, RenderTarget};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
@@ -187,6 +188,44 @@ impl Map {
|
||||
MapRenderer::render_map(canvas, atlas, map_texture);
|
||||
}
|
||||
|
||||
/// Generates Item entities for pellets and energizers from the parsed map.
|
||||
pub fn generate_items(&self, atlas: &SpriteAtlas) -> GameResult<Vec<Item>> {
|
||||
// Pre-load sprites to avoid repeated texture lookups
|
||||
let pellet_sprite = SpriteAtlas::get_tile(atlas, "maze/pellet.png")
|
||||
.ok_or_else(|| MapError::InvalidConfig("Pellet texture not found".to_string()))?;
|
||||
let energizer_sprite = SpriteAtlas::get_tile(atlas, "maze/energizer.png")
|
||||
.ok_or_else(|| MapError::InvalidConfig("Energizer texture not found".to_string()))?;
|
||||
|
||||
// Pre-allocate with estimated capacity (typical Pac-Man maps have ~240 pellets + 4 energizers)
|
||||
let mut items = Vec::with_capacity(250);
|
||||
|
||||
// Parse the raw board once
|
||||
let parsed_map = MapTileParser::parse_board(RAW_BOARD)?;
|
||||
let map = parsed_map.tiles;
|
||||
|
||||
// Iterate through the map and collect items more efficiently
|
||||
for (x, row) in map.iter().enumerate() {
|
||||
for (y, tile) in row.iter().enumerate() {
|
||||
match tile {
|
||||
MapTile::Pellet | MapTile::PowerPellet => {
|
||||
let grid_pos = IVec2::new(x as i32, y as i32);
|
||||
if let Some(&node_id) = self.grid_to_node.get(&grid_pos) {
|
||||
let (item_type, sprite) = match tile {
|
||||
MapTile::Pellet => (ItemType::Pellet, Sprite::new(pellet_sprite)),
|
||||
MapTile::PowerPellet => (ItemType::Energizer, Sprite::new(energizer_sprite)),
|
||||
_ => unreachable!(), // We already filtered for these types
|
||||
};
|
||||
items.push(Item::new(node_id, item_type, sprite));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
/// Renders a debug visualization with cursor-based highlighting.
|
||||
///
|
||||
/// This function provides interactive debugging by highlighting the nearest node
|
||||
|
||||
@@ -6,6 +6,27 @@ use sdl2::render::{Canvas, RenderTarget, Texture};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// A simple sprite for stationary items like pellets and energizers.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Sprite {
|
||||
pub atlas_tile: AtlasTile,
|
||||
}
|
||||
|
||||
impl Sprite {
|
||||
pub fn new(atlas_tile: AtlasTile) -> Self {
|
||||
Self { atlas_tile }
|
||||
}
|
||||
|
||||
pub fn render<C: RenderTarget>(&self, canvas: &mut Canvas<C>, atlas: &mut SpriteAtlas, position: glam::Vec2) -> Result<()> {
|
||||
let dest = crate::helpers::centered_with_size(
|
||||
glam::IVec2::new(position.x as i32, position.y as i32),
|
||||
glam::UVec2::new(self.atlas_tile.size.x as u32, self.atlas_tile.size.y as u32),
|
||||
);
|
||||
let mut tile = self.atlas_tile;
|
||||
tile.render(canvas, atlas, dest)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct AtlasMapper {
|
||||
pub frames: HashMap<String, MapperFrame>,
|
||||
|
||||
Reference in New Issue
Block a user