Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 09e3d85821 | |||
| c1e421bbbb | |||
| 3a9381a56c | |||
| 90bdfbd2ae |
15
.gitignore
vendored
@@ -1,8 +1,17 @@
|
|||||||
|
# IDE, Other files
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
rust-sdl2-emscripten/
|
||||||
|
|
||||||
|
# Build files
|
||||||
target/
|
target/
|
||||||
dist/
|
dist/
|
||||||
emsdk/
|
emsdk/
|
||||||
.idea
|
|
||||||
rust-sdl2-emscripten/
|
# Site build f iles
|
||||||
assets/site/build.css
|
|
||||||
tailwindcss-*
|
tailwindcss-*
|
||||||
|
assets/site/build.css
|
||||||
|
|
||||||
|
# Coverage reports
|
||||||
lcov.info
|
lcov.info
|
||||||
|
coverage.html
|
||||||
|
|||||||
17
Justfile
@@ -1,17 +1,32 @@
|
|||||||
set shell := ["bash", "-c"]
|
set shell := ["bash", "-c"]
|
||||||
set windows-shell := ["powershell.exe", "-NoLogo", "-Command"]
|
set windows-shell := ["powershell.exe", "-NoLogo", "-Command"]
|
||||||
|
|
||||||
coverage_exclude_pattern := "app.rs|audio.rs|error.rs"
|
# Regex to exclude files from coverage report, double escapes for Justfile + CLI
|
||||||
|
# You can use src\\\\..., but the filename alone is acceptable too
|
||||||
|
coverage_exclude_pattern := "src\\\\app.rs|audio.rs|src\\\\error.rs|platform\\\\emscripten.rs"
|
||||||
|
|
||||||
|
# !!! --ignore-filename-regex should be used on both reports & coverage testing
|
||||||
|
# !!! --remap-path-prefix prevents the absolute path from being used in the generated report
|
||||||
|
|
||||||
|
# Generate HTML report (for humans, source line inspection)
|
||||||
|
html: coverage
|
||||||
|
cargo llvm-cov report \
|
||||||
|
--remap-path-prefix \
|
||||||
|
--ignore-filename-regex "{{ coverage_exclude_pattern }}" \
|
||||||
|
--html \
|
||||||
|
--open
|
||||||
|
|
||||||
# Display report (for humans)
|
# Display report (for humans)
|
||||||
report-coverage: coverage
|
report-coverage: coverage
|
||||||
cargo llvm-cov report \
|
cargo llvm-cov report \
|
||||||
|
--remap-path-prefix \
|
||||||
--ignore-filename-regex "{{ coverage_exclude_pattern }}"
|
--ignore-filename-regex "{{ coverage_exclude_pattern }}"
|
||||||
|
|
||||||
# Run & generate report (for CI)
|
# Run & generate report (for CI)
|
||||||
coverage:
|
coverage:
|
||||||
cargo llvm-cov \
|
cargo llvm-cov \
|
||||||
--lcov \
|
--lcov \
|
||||||
|
--remap-path-prefix \
|
||||||
--ignore-filename-regex "{{ coverage_exclude_pattern }}" \
|
--ignore-filename-regex "{{ coverage_exclude_pattern }}" \
|
||||||
--output-path lcov.info \
|
--output-path lcov.info \
|
||||||
--profile coverage \
|
--profile coverage \
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
BIN
assets/unpacked/maze/tiles/0.png
Normal file
|
After Width: | Height: | Size: 102 B |
BIN
assets/unpacked/maze/tiles/1.png
Normal file
|
After Width: | Height: | Size: 79 B |
BIN
assets/unpacked/maze/tiles/10.png
Normal file
|
After Width: | Height: | Size: 84 B |
BIN
assets/unpacked/maze/tiles/11.png
Normal file
|
After Width: | Height: | Size: 77 B |
BIN
assets/unpacked/maze/tiles/12.png
Normal file
|
After Width: | Height: | Size: 80 B |
BIN
assets/unpacked/maze/tiles/13.png
Normal file
|
After Width: | Height: | Size: 87 B |
BIN
assets/unpacked/maze/tiles/14.png
Normal file
|
After Width: | Height: | Size: 79 B |
BIN
assets/unpacked/maze/tiles/15.png
Normal file
|
After Width: | Height: | Size: 89 B |
BIN
assets/unpacked/maze/tiles/16.png
Normal file
|
After Width: | Height: | Size: 91 B |
BIN
assets/unpacked/maze/tiles/17.png
Normal file
|
After Width: | Height: | Size: 87 B |
BIN
assets/unpacked/maze/tiles/18.png
Normal file
|
After Width: | Height: | Size: 107 B |
BIN
assets/unpacked/maze/tiles/19.png
Normal file
|
After Width: | Height: | Size: 77 B |
BIN
assets/unpacked/maze/tiles/2.png
Normal file
|
After Width: | Height: | Size: 93 B |
BIN
assets/unpacked/maze/tiles/20.png
Normal file
|
After Width: | Height: | Size: 91 B |
BIN
assets/unpacked/maze/tiles/21.png
Normal file
|
After Width: | Height: | Size: 97 B |
BIN
assets/unpacked/maze/tiles/22.png
Normal file
|
After Width: | Height: | Size: 107 B |
BIN
assets/unpacked/maze/tiles/23.png
Normal file
|
After Width: | Height: | Size: 88 B |
BIN
assets/unpacked/maze/tiles/24.png
Normal file
|
After Width: | Height: | Size: 82 B |
BIN
assets/unpacked/maze/tiles/25.png
Normal file
|
After Width: | Height: | Size: 80 B |
BIN
assets/unpacked/maze/tiles/26.png
Normal file
|
After Width: | Height: | Size: 82 B |
BIN
assets/unpacked/maze/tiles/27.png
Normal file
|
After Width: | Height: | Size: 93 B |
BIN
assets/unpacked/maze/tiles/28.png
Normal file
|
After Width: | Height: | Size: 89 B |
BIN
assets/unpacked/maze/tiles/29.png
Normal file
|
After Width: | Height: | Size: 90 B |
BIN
assets/unpacked/maze/tiles/3.png
Normal file
|
After Width: | Height: | Size: 87 B |
BIN
assets/unpacked/maze/tiles/30.png
Normal file
|
After Width: | Height: | Size: 79 B |
BIN
assets/unpacked/maze/tiles/31.png
Normal file
|
After Width: | Height: | Size: 100 B |
BIN
assets/unpacked/maze/tiles/32.png
Normal file
|
After Width: | Height: | Size: 98 B |
BIN
assets/unpacked/maze/tiles/33.png
Normal file
|
After Width: | Height: | Size: 96 B |
BIN
assets/unpacked/maze/tiles/34.png
Normal file
|
After Width: | Height: | Size: 100 B |
BIN
assets/unpacked/maze/tiles/4.png
Normal file
|
After Width: | Height: | Size: 105 B |
BIN
assets/unpacked/maze/tiles/5.png
Normal file
|
After Width: | Height: | Size: 82 B |
BIN
assets/unpacked/maze/tiles/6.png
Normal file
|
After Width: | Height: | Size: 71 B |
BIN
assets/unpacked/maze/tiles/7.png
Normal file
|
After Width: | Height: | Size: 82 B |
BIN
assets/unpacked/maze/tiles/8.png
Normal file
|
After Width: | Height: | Size: 82 B |
BIN
assets/unpacked/maze/tiles/9.png
Normal file
|
After Width: | Height: | Size: 82 B |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
@@ -18,8 +18,6 @@ pub const SCALE: f32 = 2.6;
|
|||||||
pub const BOARD_CELL_OFFSET: UVec2 = UVec2::new(0, 3);
|
pub const BOARD_CELL_OFFSET: UVec2 = UVec2::new(0, 3);
|
||||||
/// The offset of the game board from the top-left corner of the window, in pixels.
|
/// The offset of the game board from the top-left corner of the window, in pixels.
|
||||||
pub const BOARD_PIXEL_OFFSET: UVec2 = UVec2::new(BOARD_CELL_OFFSET.x * CELL_SIZE, BOARD_CELL_OFFSET.y * CELL_SIZE);
|
pub const BOARD_PIXEL_OFFSET: UVec2 = UVec2::new(BOARD_CELL_OFFSET.x * CELL_SIZE, BOARD_CELL_OFFSET.y * CELL_SIZE);
|
||||||
/// The size of the game board, in pixels.
|
|
||||||
pub const BOARD_PIXEL_SIZE: UVec2 = UVec2::new(BOARD_CELL_SIZE.x * CELL_SIZE, BOARD_CELL_SIZE.y * CELL_SIZE);
|
|
||||||
/// The size of the canvas, in pixels.
|
/// The size of the canvas, in pixels.
|
||||||
pub const CANVAS_SIZE: UVec2 = UVec2::new(
|
pub const CANVAS_SIZE: UVec2 = UVec2::new(
|
||||||
(BOARD_CELL_SIZE.x + BOARD_CELL_OFFSET.x) * CELL_SIZE,
|
(BOARD_CELL_SIZE.x + BOARD_CELL_OFFSET.x) * CELL_SIZE,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::entity::traversal::Position;
|
use crate::entity::{graph::NodeId, traversal::Position};
|
||||||
|
|
||||||
/// Trait for entities that can participate in collision detection.
|
/// Trait for entities that can participate in collision detection.
|
||||||
pub trait Collidable {
|
pub trait Collidable {
|
||||||
@@ -19,7 +19,7 @@ pub trait Collidable {
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct CollisionSystem {
|
pub struct CollisionSystem {
|
||||||
/// Maps node IDs to lists of entity IDs that are at that node
|
/// Maps node IDs to lists of entity IDs that are at that node
|
||||||
node_entities: HashMap<usize, Vec<EntityId>>,
|
node_entities: HashMap<NodeId, Vec<EntityId>>,
|
||||||
/// Maps entity IDs to their current positions
|
/// Maps entity IDs to their current positions
|
||||||
entity_positions: HashMap<EntityId, Position>,
|
entity_positions: HashMap<EntityId, Position>,
|
||||||
/// Next available entity ID
|
/// Next available entity ID
|
||||||
@@ -62,7 +62,7 @@ impl CollisionSystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Gets all entity IDs at a specific node
|
/// Gets all entity IDs at a specific node
|
||||||
pub fn entities_at_node(&self, node: usize) -> &[EntityId] {
|
pub fn entities_at_node(&self, node: NodeId) -> &[EntityId] {
|
||||||
self.node_entities.get(&node).map(|v| v.as_slice()).unwrap_or(&[])
|
self.node_entities.get(&node).map(|v| v.as_slice()).unwrap_or(&[])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ fn positions_overlap(a: &Position, b: &Position) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Gets all nodes that an entity is currently at or between.
|
/// Gets all nodes that an entity is currently at or between.
|
||||||
fn get_nodes(pos: &Position) -> SmallVec<[usize; 2]> {
|
fn get_nodes(pos: &Position) -> SmallVec<[NodeId; 2]> {
|
||||||
let mut nodes = SmallVec::new();
|
let mut nodes = SmallVec::new();
|
||||||
match pos {
|
match pos {
|
||||||
Position::AtNode(node) => nodes.push(*node),
|
Position::AtNode(node) => nodes.push(*node),
|
||||||
|
|||||||
@@ -192,14 +192,15 @@ impl Graph {
|
|||||||
|
|
||||||
// Check if the edge already exists in this direction or to the same target
|
// Check if the edge already exists in this direction or to the same target
|
||||||
if let Some(err) = adjacency_list.edges().find_map(|e| {
|
if let Some(err) = adjacency_list.edges().find_map(|e| {
|
||||||
// If we're not replacing the edge, we don't want to replace an edge that already exists in this direction
|
if !replace {
|
||||||
if !replace && e.direction == direction {
|
// If we're not replacing the edge, we don't want to replace an edge that already exists in this direction
|
||||||
Some(Err("Edge already exists in this direction."))
|
if e.direction == direction {
|
||||||
} else if e.target == to {
|
return Some(Err("Edge already exists in this direction."));
|
||||||
Some(Err("Edge already exists."))
|
} else if e.target == to {
|
||||||
} else {
|
return Some(Err("Edge already exists."));
|
||||||
None
|
}
|
||||||
}
|
}
|
||||||
|
None
|
||||||
}) {
|
}) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ impl Game {
|
|||||||
canvas.clear();
|
canvas.clear();
|
||||||
self.state
|
self.state
|
||||||
.map
|
.map
|
||||||
.render(canvas, &mut self.state.atlas, &mut self.state.map_texture);
|
.render(canvas, &mut self.state.atlas, &mut self.state.map_tiles);
|
||||||
|
|
||||||
// Render all items
|
// Render all items
|
||||||
for item in &self.state.items {
|
for item in &self.state.items {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use sdl2::{image::LoadTexture, pixels::Color, render::TextureCreator, video::WindowContext};
|
use sdl2::{image::LoadTexture, render::TextureCreator, video::WindowContext};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -6,12 +6,13 @@ use crate::{
|
|||||||
audio::Audio,
|
audio::Audio,
|
||||||
constants::RAW_BOARD,
|
constants::RAW_BOARD,
|
||||||
entity::{
|
entity::{
|
||||||
collision::{Collidable, CollisionSystem, EntityId},
|
collision::{Collidable, CollisionSystem},
|
||||||
ghost::{Ghost, GhostType},
|
ghost::{Ghost, GhostType},
|
||||||
item::Item,
|
item::Item,
|
||||||
pacman::Pacman,
|
pacman::Pacman,
|
||||||
},
|
},
|
||||||
error::{GameError, GameResult, TextureError},
|
error::{GameError, GameResult, TextureError},
|
||||||
|
game::EntityId,
|
||||||
map::Map,
|
map::Map,
|
||||||
texture::{
|
texture::{
|
||||||
sprite::{AtlasMapper, AtlasTile, SpriteAtlas},
|
sprite::{AtlasMapper, AtlasTile, SpriteAtlas},
|
||||||
@@ -28,20 +29,20 @@ use crate::{
|
|||||||
pub struct GameState {
|
pub struct GameState {
|
||||||
pub score: u32,
|
pub score: u32,
|
||||||
pub map: Map,
|
pub map: Map,
|
||||||
|
pub map_tiles: Vec<AtlasTile>,
|
||||||
pub pacman: Pacman,
|
pub pacman: Pacman,
|
||||||
|
pub pacman_id: EntityId,
|
||||||
pub ghosts: SmallVec<[Ghost; 4]>,
|
pub ghosts: SmallVec<[Ghost; 4]>,
|
||||||
|
pub ghost_ids: SmallVec<[EntityId; 4]>,
|
||||||
pub items: Vec<Item>,
|
pub items: Vec<Item>,
|
||||||
|
pub item_ids: Vec<EntityId>,
|
||||||
pub debug_mode: bool,
|
pub debug_mode: bool,
|
||||||
|
|
||||||
// Collision system
|
// Collision system
|
||||||
pub(crate) collision_system: CollisionSystem,
|
pub(crate) collision_system: CollisionSystem,
|
||||||
pub(crate) pacman_id: EntityId,
|
|
||||||
pub(crate) ghost_ids: SmallVec<[EntityId; 4]>,
|
|
||||||
pub(crate) item_ids: Vec<EntityId>,
|
|
||||||
|
|
||||||
// Rendering resources
|
// Rendering resources
|
||||||
pub(crate) atlas: SpriteAtlas,
|
pub(crate) atlas: SpriteAtlas,
|
||||||
pub(crate) map_texture: AtlasTile,
|
|
||||||
pub(crate) text_texture: TextTexture,
|
pub(crate) text_texture: TextTexture,
|
||||||
|
|
||||||
// Audio
|
// Audio
|
||||||
@@ -71,9 +72,13 @@ impl GameState {
|
|||||||
let atlas_mapper: AtlasMapper = serde_json::from_slice(&atlas_json)?;
|
let atlas_mapper: AtlasMapper = serde_json::from_slice(&atlas_json)?;
|
||||||
let atlas = SpriteAtlas::new(atlas_texture, atlas_mapper);
|
let atlas = SpriteAtlas::new(atlas_texture, atlas_mapper);
|
||||||
|
|
||||||
let mut map_texture = SpriteAtlas::get_tile(&atlas, "maze/full.png")
|
let mut map_tiles = Vec::with_capacity(35);
|
||||||
.ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound("maze/full.png".to_string())))?;
|
for i in 0..35 {
|
||||||
map_texture.color = Some(Color::RGB(0x20, 0x20, 0xf9));
|
let tile_name = format!("maze/tiles/{}.png", i);
|
||||||
|
let tile = SpriteAtlas::get_tile(&atlas, &tile_name)
|
||||||
|
.ok_or(GameError::Texture(TextureError::AtlasTileNotFound(tile_name)))?;
|
||||||
|
map_tiles.push(tile);
|
||||||
|
}
|
||||||
|
|
||||||
let text_texture = TextTexture::new(1.0);
|
let text_texture = TextTexture::new(1.0);
|
||||||
let audio = Audio::new();
|
let audio = Audio::new();
|
||||||
@@ -89,11 +94,10 @@ impl GameState {
|
|||||||
let pacman_id = collision_system.register_entity(pacman.position());
|
let pacman_id = collision_system.register_entity(pacman.position());
|
||||||
|
|
||||||
// Register items
|
// Register items
|
||||||
let mut item_ids = Vec::new();
|
let item_ids = items
|
||||||
for item in &items {
|
.iter()
|
||||||
let item_id = collision_system.register_entity(item.position());
|
.map(|item| collision_system.register_entity(item.position()))
|
||||||
item_ids.push(item_id);
|
.collect();
|
||||||
}
|
|
||||||
|
|
||||||
// Create and register ghosts
|
// Create and register ghosts
|
||||||
let ghosts = [GhostType::Blinky, GhostType::Pinky, GhostType::Inky, GhostType::Clyde]
|
let ghosts = [GhostType::Blinky, GhostType::Pinky, GhostType::Inky, GhostType::Clyde]
|
||||||
@@ -110,26 +114,27 @@ impl GameState {
|
|||||||
.map(|(ghost_type, start_node)| Ghost::new(&map.graph, *start_node, *ghost_type, &atlas))
|
.map(|(ghost_type, start_node)| Ghost::new(&map.graph, *start_node, *ghost_type, &atlas))
|
||||||
.collect::<GameResult<SmallVec<[_; 4]>>>()?;
|
.collect::<GameResult<SmallVec<[_; 4]>>>()?;
|
||||||
|
|
||||||
|
// Register ghosts
|
||||||
let ghost_ids = ghosts
|
let ghost_ids = ghosts
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ghost| collision_system.register_entity(ghost.position()))
|
.map(|ghost| collision_system.register_entity(ghost.position()))
|
||||||
.collect::<SmallVec<[_; 4]>>();
|
.collect();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
score: 0,
|
|
||||||
map,
|
map,
|
||||||
|
atlas,
|
||||||
|
map_tiles,
|
||||||
pacman,
|
pacman,
|
||||||
ghosts,
|
|
||||||
items,
|
|
||||||
debug_mode: false,
|
|
||||||
collision_system,
|
|
||||||
pacman_id,
|
pacman_id,
|
||||||
|
ghosts,
|
||||||
ghost_ids,
|
ghost_ids,
|
||||||
|
items,
|
||||||
item_ids,
|
item_ids,
|
||||||
map_texture,
|
|
||||||
text_texture,
|
text_texture,
|
||||||
audio,
|
audio,
|
||||||
atlas,
|
score: 0,
|
||||||
|
debug_mode: false,
|
||||||
|
collision_system,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,8 +158,8 @@ impl Map {
|
|||||||
///
|
///
|
||||||
/// This function draws the static map texture to the screen at the correct
|
/// This function draws the static map texture to the screen at the correct
|
||||||
/// position and scale.
|
/// position and scale.
|
||||||
pub fn render<T: RenderTarget>(&self, canvas: &mut Canvas<T>, atlas: &mut SpriteAtlas, map_texture: &mut AtlasTile) {
|
pub fn render<T: RenderTarget>(&self, canvas: &mut Canvas<T>, atlas: &mut SpriteAtlas, map_tiles: &mut [AtlasTile]) {
|
||||||
MapRenderer::render_map(canvas, atlas, map_texture);
|
MapRenderer::render_map(canvas, atlas, map_tiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates Item entities for pellets and energizers from the parsed map.
|
/// Generates Item entities for pellets and energizers from the parsed map.
|
||||||
|
|||||||
95
src/map/layout.rs
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
pub const TILE_MAP: [[usize; 28]; 31] = [
|
||||||
|
[
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 8, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5, 6, 10, 11, 11, 12, 6, 10, 11, 11, 11, 12, 6, 7, 8, 6, 10, 11, 11, 11, 12, 6, 10, 11, 11, 12, 6, 9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5, 6, 7, 6, 6, 8, 6, 7, 6, 6, 6, 8, 6, 7, 8, 6, 7, 6, 6, 6, 8, 6, 7, 6, 6, 8, 6, 9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5, 6, 13, 14, 14, 15, 6, 13, 14, 14, 14, 15, 6, 13, 15, 6, 13, 14, 14, 14, 15, 6, 13, 14, 14, 15, 6, 9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5, 6, 10, 11, 11, 12, 6, 10, 12, 6, 10, 11, 11, 11, 11, 11, 11, 12, 6, 10, 12, 6, 10, 11, 11, 12, 6, 9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5, 6, 13, 14, 14, 15, 6, 7, 8, 6, 13, 14, 14, 16, 17, 14, 14, 15, 6, 7, 8, 6, 13, 14, 14, 15, 6, 9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5, 6, 6, 6, 6, 6, 6, 7, 8, 6, 6, 6, 6, 7, 8, 6, 6, 6, 6, 7, 8, 6, 6, 6, 6, 6, 6, 9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
18, 19, 19, 19, 19, 12, 6, 7, 20, 11, 11, 12, 6, 7, 8, 6, 10, 11, 11, 21, 8, 6, 10, 19, 19, 19, 19, 22,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
6, 6, 6, 6, 6, 5, 6, 7, 17, 14, 14, 15, 6, 13, 15, 6, 13, 14, 14, 16, 8, 6, 9, 6, 6, 6, 6, 6,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
6, 6, 6, 6, 6, 5, 6, 7, 8, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 8, 6, 9, 6, 6, 6, 6, 6,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
6, 6, 6, 6, 6, 5, 6, 7, 8, 6, 23, 19, 24, 25, 25, 26, 19, 27, 6, 7, 8, 6, 9, 6, 6, 6, 6, 6,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1, 1, 1, 1, 1, 15, 6, 13, 15, 6, 9, 6, 6, 6, 6, 6, 6, 5, 6, 13, 15, 6, 13, 1, 1, 1, 1, 1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 9, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
19, 19, 19, 19, 19, 12, 6, 10, 12, 6, 9, 6, 6, 6, 6, 6, 6, 5, 6, 10, 12, 6, 10, 19, 19, 19, 19, 19,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
6, 6, 6, 6, 6, 5, 6, 7, 8, 6, 28, 1, 1, 1, 1, 1, 1, 29, 6, 7, 8, 6, 9, 6, 6, 6, 6, 6,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
6, 6, 6, 6, 6, 5, 6, 7, 8, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 8, 6, 9, 6, 6, 6, 6, 6,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
6, 6, 6, 6, 6, 5, 6, 7, 8, 6, 10, 11, 11, 11, 11, 11, 11, 12, 6, 7, 8, 6, 9, 6, 6, 6, 6, 6,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0, 1, 1, 1, 1, 15, 6, 13, 15, 6, 13, 14, 14, 16, 17, 14, 14, 15, 6, 13, 15, 6, 13, 1, 1, 1, 1, 4,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 8, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5, 6, 10, 11, 11, 12, 6, 10, 11, 11, 11, 12, 6, 7, 8, 6, 10, 11, 11, 11, 12, 6, 30, 11, 11, 12, 6, 9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5, 6, 13, 14, 16, 8, 6, 13, 14, 14, 14, 15, 6, 13, 15, 6, 13, 14, 14, 14, 15, 6, 7, 17, 14, 15, 6, 9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5, 6, 6, 6, 7, 8, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 8, 6, 6, 6, 9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
31, 11, 12, 6, 7, 8, 6, 10, 12, 6, 10, 11, 11, 11, 11, 11, 11, 12, 6, 10, 12, 6, 7, 8, 6, 10, 11, 32,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
33, 14, 15, 6, 13, 15, 6, 7, 8, 6, 13, 14, 14, 16, 17, 14, 14, 15, 6, 7, 8, 6, 13, 15, 6, 13, 14, 34,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5, 6, 6, 6, 6, 6, 6, 7, 8, 6, 6, 6, 6, 7, 8, 6, 6, 6, 6, 7, 8, 6, 6, 6, 6, 6, 6, 9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5, 6, 10, 11, 11, 11, 11, 21, 20, 11, 11, 12, 6, 7, 8, 6, 10, 11, 11, 21, 20, 11, 11, 11, 11, 12, 6, 9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5, 6, 13, 14, 14, 14, 14, 14, 14, 14, 14, 15, 6, 13, 15, 6, 13, 14, 14, 14, 14, 14, 14, 14, 14, 15, 6, 9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 22,
|
||||||
|
],
|
||||||
|
];
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
//! This module defines the game map and provides functions for interacting with it.
|
//! This module defines the game map and provides functions for interacting with it.
|
||||||
|
|
||||||
pub mod builder;
|
pub mod builder;
|
||||||
|
pub mod layout;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod render;
|
pub mod render;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
//! Map rendering functionality.
|
//! Map rendering functionality.
|
||||||
|
|
||||||
|
use crate::constants::{BOARD_CELL_OFFSET, CELL_SIZE};
|
||||||
|
use crate::map::layout::TILE_MAP;
|
||||||
use crate::texture::sprite::{AtlasTile, SpriteAtlas};
|
use crate::texture::sprite::{AtlasTile, SpriteAtlas};
|
||||||
use crate::texture::text::TextTexture;
|
use crate::texture::text::TextTexture;
|
||||||
use glam::Vec2;
|
use glam::Vec2;
|
||||||
@@ -17,15 +19,22 @@ impl MapRenderer {
|
|||||||
///
|
///
|
||||||
/// This function draws the static map texture to the screen at the correct
|
/// This function draws the static map texture to the screen at the correct
|
||||||
/// position and scale.
|
/// position and scale.
|
||||||
pub fn render_map<T: RenderTarget>(canvas: &mut Canvas<T>, atlas: &mut SpriteAtlas, map_texture: &mut AtlasTile) {
|
pub fn render_map<T: RenderTarget>(canvas: &mut Canvas<T>, atlas: &mut SpriteAtlas, map_tiles: &mut [AtlasTile]) {
|
||||||
let dest = Rect::new(
|
for (y, row) in TILE_MAP.iter().enumerate() {
|
||||||
crate::constants::BOARD_PIXEL_OFFSET.x as i32,
|
for (x, &tile_index) in row.iter().enumerate() {
|
||||||
crate::constants::BOARD_PIXEL_OFFSET.y as i32,
|
let mut tile = map_tiles[tile_index];
|
||||||
crate::constants::BOARD_PIXEL_SIZE.x,
|
tile.color = Some(Color::RGB(0x20, 0x20, 0xf9));
|
||||||
crate::constants::BOARD_PIXEL_SIZE.y,
|
let dest = Rect::new(
|
||||||
);
|
(BOARD_CELL_OFFSET.x as usize * CELL_SIZE as usize + x * CELL_SIZE as usize) as i32,
|
||||||
if let Err(e) = map_texture.render(canvas, atlas, dest) {
|
(BOARD_CELL_OFFSET.y as usize * CELL_SIZE as usize + y * CELL_SIZE as usize) as i32,
|
||||||
tracing::error!("Failed to render map: {}", e);
|
CELL_SIZE,
|
||||||
|
CELL_SIZE,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(e) = tile.render(canvas, atlas, dest) {
|
||||||
|
tracing::error!("Failed to render map tile: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,71 @@ fn test_graph_edge_permissions() {
|
|||||||
assert_eq!(edge.permissions, EdgePermissions::GhostsOnly);
|
assert_eq!(edge.permissions, EdgePermissions::GhostsOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_add_connected_node() {
|
||||||
|
let mut graph = Graph::new();
|
||||||
|
let node1 = graph.add_node(Node {
|
||||||
|
position: glam::Vec2::new(0.0, 0.0),
|
||||||
|
});
|
||||||
|
|
||||||
|
let node2 = graph
|
||||||
|
.add_connected(
|
||||||
|
node1,
|
||||||
|
Direction::Right,
|
||||||
|
Node {
|
||||||
|
position: glam::Vec2::new(16.0, 0.0),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(graph.node_count(), 2);
|
||||||
|
let edge = graph.find_edge(node1, node2);
|
||||||
|
assert!(edge.is_some());
|
||||||
|
assert_eq!(edge.unwrap().direction, Direction::Right);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_error_on_negative_edge_distance() {
|
||||||
|
let mut graph = Graph::new();
|
||||||
|
let node1 = graph.add_node(Node {
|
||||||
|
position: glam::Vec2::new(0.0, 0.0),
|
||||||
|
});
|
||||||
|
let node2 = graph.add_node(Node {
|
||||||
|
position: glam::Vec2::new(16.0, 0.0),
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = graph.add_edge(node1, node2, false, Some(-1.0), Direction::Right, EdgePermissions::All);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_error_on_duplicate_edge_without_replace() {
|
||||||
|
let mut graph = create_test_graph();
|
||||||
|
let result = graph.add_edge(0, 1, false, None, Direction::Right, EdgePermissions::All);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_allow_replacing_an_edge() {
|
||||||
|
let mut graph = create_test_graph();
|
||||||
|
let result = graph.add_edge(0, 1, true, Some(42.0), Direction::Right, EdgePermissions::All);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
let edge = graph.find_edge(0, 1).unwrap();
|
||||||
|
assert_eq!(edge.distance, 42.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_find_edge_between_nodes() {
|
||||||
|
let graph = create_test_graph();
|
||||||
|
let edge = graph.find_edge(0, 1);
|
||||||
|
assert!(edge.is_some());
|
||||||
|
assert_eq!(edge.unwrap().target, 1);
|
||||||
|
|
||||||
|
let non_existent_edge = graph.find_edge(0, 99);
|
||||||
|
assert!(non_existent_edge.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_traverser_basic() {
|
fn test_traverser_basic() {
|
||||||
let graph = create_test_graph();
|
let graph = create_test_graph();
|
||||||
|
|||||||