mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-18 12:12:39 -06:00
Compare commits
3 Commits
27079e127d
...
v0.33.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 1dc8aca373 | |||
| 02089a78da | |||
| 1f8e7c6d71 |
8
.github/workflows/build.yaml
vendored
8
.github/workflows/build.yaml
vendored
@@ -14,19 +14,19 @@ jobs:
|
|||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
target: x86_64-unknown-linux-gnu
|
target: x86_64-unknown-linux-gnu
|
||||||
artifact_name: pacman
|
artifact_name: pacman
|
||||||
toolchain: 1.88.0
|
toolchain: 1.86.0
|
||||||
- os: macos-13
|
- os: macos-13
|
||||||
target: x86_64-apple-darwin
|
target: x86_64-apple-darwin
|
||||||
artifact_name: pacman
|
artifact_name: pacman
|
||||||
toolchain: 1.88.0
|
toolchain: 1.86.0
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
target: aarch64-apple-darwin
|
target: aarch64-apple-darwin
|
||||||
artifact_name: pacman
|
artifact_name: pacman
|
||||||
toolchain: 1.88.0
|
toolchain: 1.86.0
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
target: x86_64-pc-windows-gnu
|
target: x86_64-pc-windows-gnu
|
||||||
artifact_name: pacman.exe
|
artifact_name: pacman.exe
|
||||||
toolchain: 1.88.0
|
toolchain: 1.86.0
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|||||||
2
.github/workflows/tests.yaml
vendored
2
.github/workflows/tests.yaml
vendored
@@ -4,7 +4,7 @@ on: ["push", "pull_request"]
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
RUST_TOOLCHAIN: 1.88.0
|
RUST_TOOLCHAIN: 1.86.0
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
|||||||
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[toolchain]
|
||||||
|
channel = "1.86.0"
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
use pathfinding::prelude::dijkstra;
|
use pathfinding::prelude::dijkstra;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
use crate::entity::direction::Direction;
|
use crate::entity::direction::Direction;
|
||||||
use crate::entity::graph::{Edge, EdgePermissions, Graph, NodeId};
|
use crate::entity::graph::{Edge, EdgePermissions, Graph, NodeId};
|
||||||
@@ -104,7 +105,7 @@ impl Entity for Ghost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = self.traverser.advance(graph, dt * 60.0 * self.speed, &can_ghost_traverse) {
|
if let Err(e) = self.traverser.advance(graph, dt * 60.0 * self.speed, &can_ghost_traverse) {
|
||||||
eprintln!("Ghost movement error: {}", e);
|
error!("Ghost movement error: {}", e);
|
||||||
}
|
}
|
||||||
self.texture.tick(dt);
|
self.texture.tick(dt);
|
||||||
}
|
}
|
||||||
|
|||||||
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 direction;
|
||||||
pub mod ghost;
|
pub mod ghost;
|
||||||
pub mod graph;
|
pub mod graph;
|
||||||
|
pub mod item;
|
||||||
pub mod pacman;
|
pub mod pacman;
|
||||||
pub mod r#trait;
|
pub mod r#trait;
|
||||||
pub mod traversal;
|
pub mod traversal;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use crate::texture::animated::AnimatedTexture;
|
|||||||
use crate::texture::directional::DirectionalAnimatedTexture;
|
use crate::texture::directional::DirectionalAnimatedTexture;
|
||||||
use crate::texture::sprite::SpriteAtlas;
|
use crate::texture::sprite::SpriteAtlas;
|
||||||
use sdl2::keyboard::Keycode;
|
use sdl2::keyboard::Keycode;
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
use crate::error::{GameError, GameResult, TextureError};
|
use crate::error::{GameError, GameResult, TextureError};
|
||||||
|
|
||||||
@@ -60,7 +61,7 @@ impl Entity for Pacman {
|
|||||||
|
|
||||||
fn tick(&mut self, dt: f32, graph: &Graph) {
|
fn tick(&mut self, dt: f32, graph: &Graph) {
|
||||||
if let Err(e) = self.traverser.advance(graph, dt * 60.0 * 1.125, &can_pacman_traverse) {
|
if let Err(e) = self.traverser.advance(graph, dt * 60.0 * 1.125, &can_pacman_traverse) {
|
||||||
eprintln!("Pac-Man movement error: {}", e);
|
error!("Pac-Man movement error: {}", e);
|
||||||
}
|
}
|
||||||
self.texture.tick(dt);
|
self.texture.tick(dt);
|
||||||
}
|
}
|
||||||
|
|||||||
39
src/game.rs
39
src/game.rs
@@ -18,6 +18,7 @@ use crate::{
|
|||||||
constants::{CELL_SIZE, RAW_BOARD},
|
constants::{CELL_SIZE, RAW_BOARD},
|
||||||
entity::{
|
entity::{
|
||||||
ghost::{Ghost, GhostType},
|
ghost::{Ghost, GhostType},
|
||||||
|
item::Item,
|
||||||
pacman::Pacman,
|
pacman::Pacman,
|
||||||
r#trait::Entity,
|
r#trait::Entity,
|
||||||
},
|
},
|
||||||
@@ -37,6 +38,7 @@ pub struct Game {
|
|||||||
pub map: Map,
|
pub map: Map,
|
||||||
pub pacman: Pacman,
|
pub pacman: Pacman,
|
||||||
pub ghosts: Vec<Ghost>,
|
pub ghosts: Vec<Ghost>,
|
||||||
|
pub items: Vec<Item>,
|
||||||
pub debug_mode: bool,
|
pub debug_mode: bool,
|
||||||
|
|
||||||
// Rendering resources
|
// Rendering resources
|
||||||
@@ -68,7 +70,7 @@ impl Game {
|
|||||||
let atlas_texture = unsafe {
|
let atlas_texture = unsafe {
|
||||||
let texture = texture_creator.load_texture_bytes(&atlas_bytes).map_err(|e| {
|
let texture = texture_creator.load_texture_bytes(&atlas_bytes).map_err(|e| {
|
||||||
if e.to_string().contains("format") || e.to_string().contains("unsupported") {
|
if e.to_string().contains("format") || e.to_string().contains("unsupported") {
|
||||||
GameError::Texture(TextureError::InvalidFormat(format!("Unsupported texture format: {}", e)))
|
GameError::Texture(TextureError::InvalidFormat(format!("Unsupported texture format: {e}")))
|
||||||
} else {
|
} else {
|
||||||
GameError::Texture(TextureError::LoadFailed(e.to_string()))
|
GameError::Texture(TextureError::LoadFailed(e.to_string()))
|
||||||
}
|
}
|
||||||
@@ -87,6 +89,9 @@ impl Game {
|
|||||||
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)?;
|
||||||
|
|
||||||
|
// Generate items (pellets and energizers)
|
||||||
|
let items = map.generate_items(&atlas)?;
|
||||||
|
|
||||||
// Create ghosts at random positions
|
// Create ghosts at random positions
|
||||||
let mut ghosts = Vec::new();
|
let mut ghosts = Vec::new();
|
||||||
let ghost_types = [GhostType::Blinky, GhostType::Pinky, GhostType::Inky, GhostType::Clyde];
|
let ghost_types = [GhostType::Blinky, GhostType::Pinky, GhostType::Inky, GhostType::Clyde];
|
||||||
@@ -94,6 +99,7 @@ impl Game {
|
|||||||
|
|
||||||
if map.graph.node_count() == 0 {
|
if map.graph.node_count() == 0 {
|
||||||
return Err(GameError::Config("Game map has no nodes - invalid configuration".to_string()));
|
return Err(GameError::Config("Game map has no nodes - invalid configuration".to_string()));
|
||||||
|
// TODO: This is a bug, we should handle this better
|
||||||
}
|
}
|
||||||
|
|
||||||
for &ghost_type in &ghost_types {
|
for &ghost_type in &ghost_types {
|
||||||
@@ -108,6 +114,7 @@ impl Game {
|
|||||||
map,
|
map,
|
||||||
pacman,
|
pacman,
|
||||||
ghosts,
|
ghosts,
|
||||||
|
items,
|
||||||
debug_mode: false,
|
debug_mode: false,
|
||||||
map_texture,
|
map_texture,
|
||||||
text_texture,
|
text_texture,
|
||||||
@@ -145,6 +152,9 @@ impl Game {
|
|||||||
|
|
||||||
self.pacman = Pacman::new(&self.map.graph, pacman_start_node, &self.atlas)?;
|
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
|
// Randomize ghost positions
|
||||||
let ghost_types = [GhostType::Blinky, GhostType::Pinky, GhostType::Inky, GhostType::Clyde];
|
let ghost_types = [GhostType::Blinky, GhostType::Pinky, GhostType::Inky, GhostType::Clyde];
|
||||||
let mut rng = SmallRng::from_os_rng();
|
let mut rng = SmallRng::from_os_rng();
|
||||||
@@ -164,6 +174,26 @@ impl Game {
|
|||||||
for ghost in &mut self.ghosts {
|
for ghost in &mut self.ghosts {
|
||||||
ghost.tick(dt, &self.map.graph);
|
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<()> {
|
pub fn draw<T: RenderTarget>(&mut self, canvas: &mut Canvas<T>, backbuffer: &mut Texture) -> GameResult<()> {
|
||||||
@@ -173,6 +203,13 @@ impl Game {
|
|||||||
canvas.clear();
|
canvas.clear();
|
||||||
self.map.render(canvas, &mut self.atlas, &mut self.map_texture);
|
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
|
// Render all ghosts
|
||||||
for ghost in &self.ghosts {
|
for ghost in &self.ghosts {
|
||||||
if let Err(e) = ghost.render(canvas, &mut self.atlas, &self.map.graph) {
|
if let Err(e) = ghost.render(canvas, &mut self.atlas, &self.map.graph) {
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
//! Map construction and building functionality.
|
//! 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::direction::Direction;
|
||||||
use crate::entity::graph::{EdgePermissions, Graph, Node, NodeId};
|
use crate::entity::graph::{EdgePermissions, Graph, Node, NodeId};
|
||||||
|
use crate::entity::item::{Item, ItemType};
|
||||||
use crate::map::parser::MapTileParser;
|
use crate::map::parser::MapTileParser;
|
||||||
use crate::map::render::MapRenderer;
|
use crate::map::render::MapRenderer;
|
||||||
use crate::texture::sprite::{AtlasTile, SpriteAtlas};
|
use crate::texture::sprite::{AtlasTile, Sprite, SpriteAtlas};
|
||||||
use glam::{IVec2, UVec2, Vec2};
|
use glam::{IVec2, UVec2, Vec2};
|
||||||
use sdl2::render::{Canvas, RenderTarget};
|
use sdl2::render::{Canvas, RenderTarget};
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
@@ -117,7 +118,7 @@ impl Map {
|
|||||||
// Connect the new node to the source node
|
// Connect the new node to the source node
|
||||||
graph
|
graph
|
||||||
.connect(*source_node_id, new_node_id, false, None, dir)
|
.connect(*source_node_id, new_node_id, false, None, dir)
|
||||||
.map_err(|e| MapError::InvalidConfig(format!("Failed to add edge: {}", e)))?;
|
.map_err(|e| MapError::InvalidConfig(format!("Failed to add edge: {e}")))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,7 +133,7 @@ impl Map {
|
|||||||
if let Some(&neighbor_id) = grid_to_node.get(&neighbor) {
|
if let Some(&neighbor_id) = grid_to_node.get(&neighbor) {
|
||||||
graph
|
graph
|
||||||
.connect(node_id, neighbor_id, false, None, dir)
|
.connect(node_id, neighbor_id, false, None, dir)
|
||||||
.map_err(|e| MapError::InvalidConfig(format!("Failed to add edge: {}", e)))?;
|
.map_err(|e| MapError::InvalidConfig(format!("Failed to add edge: {e}")))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,6 +188,44 @@ impl Map {
|
|||||||
MapRenderer::render_map(canvas, atlas, map_texture);
|
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.
|
/// Renders a debug visualization with cursor-based highlighting.
|
||||||
///
|
///
|
||||||
/// This function provides interactive debugging by highlighting the nearest node
|
/// This function provides interactive debugging by highlighting the nearest node
|
||||||
@@ -241,10 +280,10 @@ impl Map {
|
|||||||
// Connect the house door to the left and right nodes
|
// Connect the house door to the left and right nodes
|
||||||
graph
|
graph
|
||||||
.connect(node_id, *left_node, true, None, Direction::Left)
|
.connect(node_id, *left_node, true, None, Direction::Left)
|
||||||
.map_err(|e| MapError::InvalidConfig(format!("Failed to connect house door to left node: {}", e)))?;
|
.map_err(|e| MapError::InvalidConfig(format!("Failed to connect house door to left node: {e}")))?;
|
||||||
graph
|
graph
|
||||||
.connect(node_id, *right_node, true, None, Direction::Right)
|
.connect(node_id, *right_node, true, None, Direction::Right)
|
||||||
.map_err(|e| MapError::InvalidConfig(format!("Failed to connect house door to right node: {}", e)))?;
|
.map_err(|e| MapError::InvalidConfig(format!("Failed to connect house door to right node: {e}")))?;
|
||||||
|
|
||||||
(node_id, node_position)
|
(node_id, node_position)
|
||||||
};
|
};
|
||||||
@@ -263,10 +302,10 @@ impl Map {
|
|||||||
// Connect the center node to the top and bottom nodes
|
// Connect the center node to the top and bottom nodes
|
||||||
graph
|
graph
|
||||||
.connect(center_node_id, top_node_id, false, None, Direction::Up)
|
.connect(center_node_id, top_node_id, false, None, Direction::Up)
|
||||||
.map_err(|e| MapError::InvalidConfig(format!("Failed to connect house line to top node: {}", e)))?;
|
.map_err(|e| MapError::InvalidConfig(format!("Failed to connect house line to top node: {e}")))?;
|
||||||
graph
|
graph
|
||||||
.connect(center_node_id, bottom_node_id, false, None, Direction::Down)
|
.connect(center_node_id, bottom_node_id, false, None, Direction::Down)
|
||||||
.map_err(|e| MapError::InvalidConfig(format!("Failed to connect house line to bottom node: {}", e)))?;
|
.map_err(|e| MapError::InvalidConfig(format!("Failed to connect house line to bottom node: {e}")))?;
|
||||||
|
|
||||||
Ok((center_node_id, top_node_id))
|
Ok((center_node_id, top_node_id))
|
||||||
};
|
};
|
||||||
@@ -289,7 +328,7 @@ impl Map {
|
|||||||
Direction::Down,
|
Direction::Down,
|
||||||
EdgePermissions::GhostsOnly,
|
EdgePermissions::GhostsOnly,
|
||||||
)
|
)
|
||||||
.map_err(|e| MapError::InvalidConfig(format!("Failed to create ghost-only entrance to house: {}", e)))?;
|
.map_err(|e| MapError::InvalidConfig(format!("Failed to create ghost-only entrance to house: {e}")))?;
|
||||||
|
|
||||||
graph
|
graph
|
||||||
.add_edge(
|
.add_edge(
|
||||||
@@ -300,7 +339,7 @@ impl Map {
|
|||||||
Direction::Up,
|
Direction::Up,
|
||||||
EdgePermissions::GhostsOnly,
|
EdgePermissions::GhostsOnly,
|
||||||
)
|
)
|
||||||
.map_err(|e| MapError::InvalidConfig(format!("Failed to create ghost-only exit from house: {}", e)))?;
|
.map_err(|e| MapError::InvalidConfig(format!("Failed to create ghost-only exit from house: {e}")))?;
|
||||||
|
|
||||||
// Create the left line
|
// Create the left line
|
||||||
let (left_center_node_id, _) = create_house_line(
|
let (left_center_node_id, _) = create_house_line(
|
||||||
@@ -319,11 +358,11 @@ impl Map {
|
|||||||
// Connect the center line to the left and right lines
|
// Connect the center line to the left and right lines
|
||||||
graph
|
graph
|
||||||
.connect(center_center_node_id, left_center_node_id, false, None, Direction::Left)
|
.connect(center_center_node_id, left_center_node_id, false, None, Direction::Left)
|
||||||
.map_err(|e| MapError::InvalidConfig(format!("Failed to connect house entrance to left top line: {}", e)))?;
|
.map_err(|e| MapError::InvalidConfig(format!("Failed to connect house entrance to left top line: {e}")))?;
|
||||||
|
|
||||||
graph
|
graph
|
||||||
.connect(center_center_node_id, right_center_node_id, false, None, Direction::Right)
|
.connect(center_center_node_id, right_center_node_id, false, None, Direction::Right)
|
||||||
.map_err(|e| MapError::InvalidConfig(format!("Failed to connect house entrance to right top line: {}", e)))?;
|
.map_err(|e| MapError::InvalidConfig(format!("Failed to connect house entrance to right top line: {e}")))?;
|
||||||
|
|
||||||
debug!("House entrance node id: {house_entrance_node_id}");
|
debug!("House entrance node id: {house_entrance_node_id}");
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,27 @@ use sdl2::render::{Canvas, RenderTarget, Texture};
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
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)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
pub struct AtlasMapper {
|
pub struct AtlasMapper {
|
||||||
pub frames: HashMap<String, MapperFrame>,
|
pub frames: HashMap<String, MapperFrame>,
|
||||||
|
|||||||
Reference in New Issue
Block a user