Compare commits

...

4 Commits

15 changed files with 590 additions and 66 deletions

26
Cargo.lock generated
View File

@@ -89,6 +89,12 @@ version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "indexmap"
version = "2.10.0"
@@ -194,6 +200,8 @@ dependencies = [
"serde_json",
"smallvec",
"spin_sleep",
"strum",
"strum_macros",
"thiserror 1.0.69",
"tracing",
"tracing-error",
@@ -406,6 +414,24 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "strum"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
[[package]]
name = "strum_macros"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "2.0.104"

View File

@@ -21,6 +21,8 @@ glam = { version = "0.30.4", features = [] }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.141"
smallvec = "1.15.1"
strum = "0.27.2"
strum_macros = "0.27.2"
[profile.release]
lto = true

View File

@@ -1,2 +1,3 @@
[toolchain]
channel = "1.86.0"
components = ["rustfmt", "llvm-tools-preview", "clippy"]

128
src/entity/collision.rs Normal file
View File

@@ -0,0 +1,128 @@
use smallvec::SmallVec;
use std::collections::HashMap;
use crate::entity::traversal::Position;
/// Trait for entities that can participate in collision detection.
pub trait Collidable {
/// Returns the current position of this entity.
fn position(&self) -> Position;
/// Checks if this entity is colliding with another entity.
#[allow(dead_code)]
fn is_colliding_with(&self, other: &dyn Collidable) -> bool {
positions_overlap(&self.position(), &other.position())
}
}
/// System for tracking entities by their positions for efficient collision detection.
#[derive(Default)]
pub struct CollisionSystem {
/// Maps node IDs to lists of entity IDs that are at that node
node_entities: HashMap<usize, Vec<EntityId>>,
/// Maps entity IDs to their current positions
entity_positions: HashMap<EntityId, Position>,
/// Next available entity ID
next_id: EntityId,
}
/// Unique identifier for an entity in the collision system
pub type EntityId = u32;
impl CollisionSystem {
/// Registers an entity with the collision system and returns its ID
pub fn register_entity(&mut self, position: Position) -> EntityId {
let id = self.next_id;
self.next_id += 1;
self.entity_positions.insert(id, position);
self.update_node_entities(id, position);
id
}
/// Updates an entity's position
pub fn update_position(&mut self, entity_id: EntityId, new_position: Position) {
if let Some(old_position) = self.entity_positions.get(&entity_id) {
// Remove from old nodes
self.remove_from_nodes(entity_id, *old_position);
}
// Update position and add to new nodes
self.entity_positions.insert(entity_id, new_position);
self.update_node_entities(entity_id, new_position);
}
/// Removes an entity from the collision system
#[allow(dead_code)]
pub fn remove_entity(&mut self, entity_id: EntityId) {
if let Some(position) = self.entity_positions.remove(&entity_id) {
self.remove_from_nodes(entity_id, position);
}
}
/// Gets all entity IDs at a specific node
pub fn entities_at_node(&self, node: usize) -> &[EntityId] {
self.node_entities.get(&node).map(|v| v.as_slice()).unwrap_or(&[])
}
/// Gets all entity IDs that could collide with an entity at the given position
pub fn potential_collisions(&self, position: &Position) -> Vec<EntityId> {
let mut collisions = Vec::new();
let nodes = get_nodes(position);
for node in nodes {
collisions.extend(self.entities_at_node(node));
}
// Remove duplicates
collisions.sort_unstable();
collisions.dedup();
collisions
}
/// Updates the node_entities map when an entity's position changes
fn update_node_entities(&mut self, entity_id: EntityId, position: Position) {
let nodes = get_nodes(&position);
for node in nodes {
self.node_entities.entry(node).or_default().push(entity_id);
}
}
/// Removes an entity from all nodes it was previously at
fn remove_from_nodes(&mut self, entity_id: EntityId, position: Position) {
let nodes = get_nodes(&position);
for node in nodes {
if let Some(entities) = self.node_entities.get_mut(&node) {
entities.retain(|&id| id != entity_id);
if entities.is_empty() {
self.node_entities.remove(&node);
}
}
}
}
}
/// Checks if two positions overlap (entities are at the same location).
fn positions_overlap(a: &Position, b: &Position) -> bool {
let a_nodes = get_nodes(a);
let b_nodes = get_nodes(b);
// Check if any nodes overlap
a_nodes.iter().any(|a_node| b_nodes.contains(a_node))
// TODO: More complex overlap detection, the above is a simple check, but it could become an early filter for more precise calculations later
}
/// Gets all nodes that an entity is currently at or between.
fn get_nodes(pos: &Position) -> SmallVec<[usize; 2]> {
let mut nodes = SmallVec::new();
match pos {
Position::AtNode(node) => nodes.push(*node),
Position::BetweenNodes { from, to, .. } => {
nodes.push(*from);
nodes.push(*to);
}
}
nodes
}

View File

@@ -9,10 +9,13 @@ use rand::prelude::*;
use smallvec::SmallVec;
use tracing::error;
use crate::entity::direction::Direction;
use crate::entity::graph::{Edge, EdgePermissions, Graph, NodeId};
use crate::entity::r#trait::Entity;
use crate::entity::traversal::Traverser;
use crate::entity::{
collision::Collidable,
direction::Direction,
graph::{Edge, EdgePermissions, Graph, NodeId},
r#trait::Entity,
traversal::Traverser,
};
use crate::texture::animated::AnimatedTexture;
use crate::texture::directional::DirectionalAnimatedTexture;
use crate::texture::sprite::SpriteAtlas;
@@ -245,3 +248,9 @@ impl Ghost {
}
}
}
impl Collidable for Ghost {
fn position(&self) -> crate::entity::traversal::Position {
self.traverser.position
}
}

View File

@@ -1,10 +1,11 @@
use crate::{
constants,
entity::graph::Graph,
entity::{collision::Collidable, graph::Graph},
error::EntityError,
texture::sprite::{Sprite, SpriteAtlas},
};
use sdl2::render::{Canvas, RenderTarget};
use strum_macros::{EnumCount, EnumIter};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ItemType {
@@ -26,7 +27,7 @@ impl ItemType {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCount)]
#[allow(dead_code)]
pub enum FruitKind {
Apple,
@@ -39,6 +40,19 @@ pub enum FruitKind {
}
impl FruitKind {
#[allow(dead_code)]
pub fn index(self) -> u8 {
match self {
FruitKind::Apple => 0,
FruitKind::Strawberry => 1,
FruitKind::Orange => 2,
FruitKind::Melon => 3,
FruitKind::Bell => 4,
FruitKind::Key => 5,
FruitKind::Galaxian => 6,
}
}
pub fn get_score(self) -> u32 {
match self {
FruitKind::Apple => 100,
@@ -93,3 +107,9 @@ impl Item {
}
}
}
impl Collidable for Item {
fn position(&self) -> crate::entity::traversal::Position {
crate::entity::traversal::Position::AtNode(self.node_index)
}
}

View File

@@ -1,3 +1,4 @@
pub mod collision;
pub mod direction;
pub mod ghost;
pub mod graph;

View File

@@ -4,10 +4,13 @@
//! animation, and rendering. Pac-Man moves through the game graph using
//! a traverser and displays directional animated textures.
use crate::entity::direction::Direction;
use crate::entity::graph::{Edge, EdgePermissions, Graph, NodeId};
use crate::entity::r#trait::Entity;
use crate::entity::traversal::Traverser;
use crate::entity::{
collision::Collidable,
direction::Direction,
graph::{Edge, EdgePermissions, Graph, NodeId},
r#trait::Entity,
traversal::Traverser,
};
use crate::texture::animated::AnimatedTexture;
use crate::texture::directional::DirectionalAnimatedTexture;
use crate::texture::sprite::SpriteAtlas;
@@ -125,3 +128,9 @@ impl Pacman {
}
}
}
impl Collidable for Pacman {
fn position(&self) -> crate::entity::traversal::Position {
self.traverser.position
}
}

View File

@@ -17,6 +17,7 @@ use crate::{
audio::Audio,
constants::{CELL_SIZE, RAW_BOARD},
entity::{
collision::{Collidable, CollisionSystem, EntityId},
ghost::{Ghost, GhostType},
item::Item,
pacman::Pacman,
@@ -41,6 +42,12 @@ pub struct Game {
pub items: Vec<Item>,
pub debug_mode: bool,
// Collision system
collision_system: CollisionSystem,
pacman_id: EntityId,
ghost_ids: Vec<EntityId>,
item_ids: Vec<EntityId>,
// Rendering resources
atlas: SpriteAtlas,
map_texture: AtlasTile,
@@ -109,6 +116,26 @@ impl Game {
ghosts.push(ghost);
}
// Initialize collision system
let mut collision_system = CollisionSystem::default();
// Register Pac-Man
let pacman_id = collision_system.register_entity(pacman.position());
// Register items
let mut item_ids = Vec::new();
for item in &items {
let item_id = collision_system.register_entity(item.position());
item_ids.push(item_id);
}
// Register ghosts
let mut ghost_ids = Vec::new();
for ghost in &ghosts {
let ghost_id = collision_system.register_entity(ghost.position());
ghost_ids.push(ghost_id);
}
Ok(Game {
score: 0,
map,
@@ -116,6 +143,10 @@ impl Game {
ghosts,
items,
debug_mode: false,
collision_system,
pacman_id,
ghost_ids,
item_ids,
map_texture,
text_texture,
audio,
@@ -164,6 +195,26 @@ impl Game {
*ghost = Ghost::new(&self.map.graph, random_node, ghost_types[i], &self.atlas)?;
}
// Reset collision system
self.collision_system = CollisionSystem::default();
// Re-register Pac-Man
self.pacman_id = self.collision_system.register_entity(self.pacman.position());
// Re-register items
self.item_ids.clear();
for item in &self.items {
let item_id = self.collision_system.register_entity(item.position());
self.item_ids.push(item_id);
}
// Re-register ghosts
self.ghost_ids.clear();
for ghost in &self.ghosts {
let ghost_id = self.collision_system.register_entity(ghost.position());
self.ghost_ids.push(ghost_id);
}
Ok(())
}
@@ -175,27 +226,61 @@ impl Game {
ghost.tick(dt, &self.map.graph);
}
// Check for item collisions
self.check_item_collisions();
// Update collision system positions
self.update_collision_positions();
// Check for collisions
self.check_collisions();
}
fn check_item_collisions(&mut self) {
let pacman_node = self.pacman.current_node_id();
fn update_collision_positions(&mut self) {
// Update Pac-Man's position
self.collision_system.update_position(self.pacman_id, self.pacman.position());
for item in &mut self.items {
if !item.is_collected() && item.node_index == pacman_node {
item.collect();
self.score += item.get_score();
// Update ghost positions
for (ghost, &ghost_id) in self.ghosts.iter().zip(&self.ghost_ids) {
self.collision_system.update_position(ghost_id, ghost.position());
}
}
// 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.");
fn check_collisions(&mut self) {
// Check Pac-Man vs Items
let potential_collisions = self.collision_system.potential_collisions(&self.pacman.position());
for entity_id in potential_collisions {
if entity_id != self.pacman_id {
// Check if this is an item collision
if let Some(item_index) = self.find_item_by_id(entity_id) {
let item = &mut self.items[item_index];
if !item.is_collected() {
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.");
}
}
}
// Check if this is a ghost collision
if let Some(_ghost_index) = self.find_ghost_by_id(entity_id) {
// TODO: Handle Pac-Man being eaten by ghost
tracing::info!("Pac-Man collided with ghost!");
}
}
}
}
fn find_item_by_id(&self, entity_id: EntityId) -> Option<usize> {
self.item_ids.iter().position(|&id| id == entity_id)
}
fn find_ghost_by_id(&self, entity_id: EntityId) -> Option<usize> {
self.ghost_ids.iter().position(|&id| id == entity_id)
}
pub fn draw<T: RenderTarget>(&mut self, canvas: &mut Canvas<T>, backbuffer: &mut Texture) -> GameResult<()> {
canvas
.with_texture_canvas(backbuffer, |canvas| {

119
tests/collision.rs Normal file
View File

@@ -0,0 +1,119 @@
use pacman::entity::collision::{Collidable, CollisionSystem};
use pacman::entity::traversal::Position;
struct MockCollidable {
pos: Position,
}
impl Collidable for MockCollidable {
fn position(&self) -> Position {
self.pos
}
}
#[test]
fn test_is_colliding_with() {
let entity1 = MockCollidable {
pos: Position::AtNode(1),
};
let entity2 = MockCollidable {
pos: Position::AtNode(1),
};
let entity3 = MockCollidable {
pos: Position::AtNode(2),
};
let entity4 = MockCollidable {
pos: Position::BetweenNodes {
from: 1,
to: 2,
traversed: 0.5,
},
};
assert!(entity1.is_colliding_with(&entity2));
assert!(!entity1.is_colliding_with(&entity3));
assert!(entity1.is_colliding_with(&entity4));
assert!(entity3.is_colliding_with(&entity4));
}
#[test]
fn test_collision_system_register_and_query() {
let mut collision_system = CollisionSystem::default();
let pos1 = Position::AtNode(1);
let entity1 = collision_system.register_entity(pos1);
let pos2 = Position::BetweenNodes {
from: 1,
to: 2,
traversed: 0.5,
};
let entity2 = collision_system.register_entity(pos2);
let pos3 = Position::AtNode(3);
let entity3 = collision_system.register_entity(pos3);
// Test entities_at_node
assert_eq!(collision_system.entities_at_node(1), &[entity1, entity2]);
assert_eq!(collision_system.entities_at_node(2), &[entity2]);
assert_eq!(collision_system.entities_at_node(3), &[entity3]);
assert_eq!(collision_system.entities_at_node(4), &[] as &[u32]);
// Test potential_collisions
let mut collisions1 = collision_system.potential_collisions(&pos1);
collisions1.sort_unstable();
assert_eq!(collisions1, vec![entity1, entity2]);
let mut collisions2 = collision_system.potential_collisions(&pos2);
collisions2.sort_unstable();
assert_eq!(collisions2, vec![entity1, entity2]);
let mut collisions3 = collision_system.potential_collisions(&pos3);
collisions3.sort_unstable();
assert_eq!(collisions3, vec![entity3]);
}
#[test]
fn test_collision_system_update() {
let mut collision_system = CollisionSystem::default();
let entity1 = collision_system.register_entity(Position::AtNode(1));
assert_eq!(collision_system.entities_at_node(1), &[entity1]);
assert_eq!(collision_system.entities_at_node(2), &[] as &[u32]);
collision_system.update_position(entity1, Position::AtNode(2));
assert_eq!(collision_system.entities_at_node(1), &[] as &[u32]);
assert_eq!(collision_system.entities_at_node(2), &[entity1]);
collision_system.update_position(
entity1,
Position::BetweenNodes {
from: 2,
to: 3,
traversed: 0.1,
},
);
assert_eq!(collision_system.entities_at_node(1), &[] as &[u32]);
assert_eq!(collision_system.entities_at_node(2), &[entity1]);
assert_eq!(collision_system.entities_at_node(3), &[entity1]);
}
#[test]
fn test_collision_system_remove() {
let mut collision_system = CollisionSystem::default();
let entity1 = collision_system.register_entity(Position::AtNode(1));
let entity2 = collision_system.register_entity(Position::AtNode(1));
assert_eq!(collision_system.entities_at_node(1), &[entity1, entity2]);
collision_system.remove_entity(entity1);
assert_eq!(collision_system.entities_at_node(1), &[entity2]);
collision_system.remove_entity(entity2);
assert_eq!(collision_system.entities_at_node(1), &[] as &[u32]);
}

View File

@@ -52,3 +52,26 @@ fn test_directional_texture_all_directions() {
assert!(texture.has_direction(*direction));
}
}
#[test]
fn test_directional_texture_stopped() {
let mut stopped_textures = [None, None, None, None];
stopped_textures[Direction::Up.as_usize()] = Some(mock_animated_texture(1));
let texture = DirectionalAnimatedTexture::new([None, None, None, None], stopped_textures);
assert_eq!(texture.stopped_texture_count(), 1);
assert!(texture.has_stopped_direction(Direction::Up));
assert!(!texture.has_stopped_direction(Direction::Down));
}
#[test]
fn test_directional_texture_tick() {
let mut textures = [None, None, None, None];
textures[Direction::Up.as_usize()] = Some(mock_animated_texture(1));
let mut texture = DirectionalAnimatedTexture::new(textures, [None, None, None, None]);
// This is a bit of a placeholder, since we can't inspect the inner state easily.
// We're just ensuring the tick method runs without panicking.
texture.tick(0.1);
}

View File

@@ -1,6 +1,9 @@
use pacman::constants::RAW_BOARD;
use pacman::map::Map;
mod collision;
mod item;
#[test]
fn test_game_map_creation() {
let map = Map::new(RAW_BOARD).unwrap();

53
tests/item.rs Normal file
View File

@@ -0,0 +1,53 @@
use glam::U16Vec2;
use pacman::{
entity::{
collision::Collidable,
item::{FruitKind, Item, ItemType},
},
texture::sprite::{AtlasTile, Sprite},
};
use strum::{EnumCount, IntoEnumIterator};
#[test]
fn test_item_type_get_score() {
assert_eq!(ItemType::Pellet.get_score(), 10);
assert_eq!(ItemType::Energizer.get_score(), 50);
let fruit = ItemType::Fruit { kind: FruitKind::Apple };
assert_eq!(fruit.get_score(), 100);
}
#[test]
fn test_fruit_kind_increasing_score() {
// Build a list of fruit kinds, sorted by their index
let mut kinds = FruitKind::iter()
.map(|kind| (kind.index(), kind.get_score()))
.collect::<Vec<_>>();
kinds.sort_unstable_by_key(|(index, _)| *index);
assert_eq!(kinds.len(), FruitKind::COUNT as usize);
// Check that the score increases as expected
for window in kinds.windows(2) {
let ((_, prev), (_, next)) = (window[0], window[1]);
assert!(prev < next, "Fruits should have increasing scores, but {prev:?} < {next:?}");
}
}
#[test]
fn test_item_creation_and_collection() {
let atlas_tile = AtlasTile {
pos: U16Vec2::new(0, 0),
size: U16Vec2::new(16, 16),
color: None,
};
let sprite = Sprite::new(atlas_tile);
let mut item = Item::new(0, ItemType::Pellet, sprite);
assert!(!item.is_collected());
assert_eq!(item.get_score(), 10);
assert_eq!(item.position().from_node_id(), 0);
item.collect();
assert!(item.is_collected());
}

View File

@@ -1,47 +1,11 @@
use glam::Vec2;
use pacman::constants::{BOARD_CELL_SIZE, CELL_SIZE};
use pacman::constants::{BOARD_CELL_SIZE, CELL_SIZE, RAW_BOARD};
use pacman::map::Map;
fn create_minimal_test_board() -> [&'static str; BOARD_CELL_SIZE.y as usize] {
let mut board = [""; BOARD_CELL_SIZE.y as usize];
board[0] = "############################";
board[1] = "#............##............#";
board[2] = "#.####.#####.##.#####.####.#";
board[3] = "#o####.#####.##.#####.####o#";
board[4] = "#.####.#####.##.#####.####.#";
board[5] = "#..........................#";
board[6] = "#.####.##.########.##.####.#";
board[7] = "#.####.##.########.##.####.#";
board[8] = "#......##....##....##......#";
board[9] = "######.##### ## #####.######";
board[10] = " #.##### ## #####.# ";
board[11] = " #.## == ##.# ";
board[12] = " #.## ######## ##.# ";
board[13] = "######.## ######## ##.######";
board[14] = "T . ######## . T";
board[15] = "######.## ######## ##.######";
board[16] = " #.## ######## ##.# ";
board[17] = " #.## ##.# ";
board[18] = " #.## ######## ##.# ";
board[19] = "######.## ######## ##.######";
board[20] = "#............##............#";
board[21] = "#.####.#####.##.#####.####.#";
board[22] = "#.####.#####.##.#####.####.#";
board[23] = "#o..##.......X .......##..o#";
board[24] = "###.##.##.########.##.##.###";
board[25] = "###.##.##.########.##.##.###";
board[26] = "#......##....##....##......#";
board[27] = "#.##########.##.##########.#";
board[28] = "#.##########.##.##########.#";
board[29] = "#..........................#";
board[30] = "############################";
board
}
use sdl2::render::Texture;
#[test]
fn test_map_creation() {
let board = create_minimal_test_board();
let map = Map::new(board).unwrap();
let map = Map::new(RAW_BOARD).unwrap();
assert!(map.graph.node_count() > 0);
assert!(!map.grid_to_node.is_empty());
@@ -59,8 +23,7 @@ fn test_map_creation() {
#[test]
fn test_map_starting_positions() {
let board = create_minimal_test_board();
let map = Map::new(board).unwrap();
let map = Map::new(RAW_BOARD).unwrap();
let pacman_pos = map.find_starting_position(0);
assert!(pacman_pos.is_some());
@@ -73,8 +36,7 @@ fn test_map_starting_positions() {
#[test]
fn test_map_node_positions() {
let board = create_minimal_test_board();
let map = Map::new(board).unwrap();
let map = Map::new(RAW_BOARD).unwrap();
for (grid_pos, &node_id) in &map.grid_to_node {
let node = map.graph.get_node(node_id).unwrap();
@@ -84,3 +46,61 @@ fn test_map_node_positions() {
assert_eq!(node.position, expected_pos);
}
}
#[test]
fn test_generate_items() {
use pacman::texture::sprite::{AtlasMapper, MapperFrame, SpriteAtlas};
use std::collections::HashMap;
let map = Map::new(RAW_BOARD).unwrap();
// Create a minimal atlas for testing
let mut frames = HashMap::new();
frames.insert(
"maze/pellet.png".to_string(),
MapperFrame {
x: 0,
y: 0,
width: 8,
height: 8,
},
);
frames.insert(
"maze/energizer.png".to_string(),
MapperFrame {
x: 8,
y: 0,
width: 8,
height: 8,
},
);
let mapper = AtlasMapper { frames };
let texture = unsafe { std::mem::transmute::<usize, Texture<'static>>(0usize) };
let atlas = SpriteAtlas::new(texture, mapper);
let items = map.generate_items(&atlas).unwrap();
// Verify we have items
assert!(!items.is_empty());
// Count different types
let pellet_count = items
.iter()
.filter(|item| matches!(item.item_type, pacman::entity::item::ItemType::Pellet))
.count();
let energizer_count = items
.iter()
.filter(|item| matches!(item.item_type, pacman::entity::item::ItemType::Energizer))
.count();
// Should have both types
assert_eq!(pellet_count, 240);
assert_eq!(energizer_count, 4);
// All items should be uncollected initially
assert!(items.iter().all(|item| !item.is_collected()));
// All items should have valid node indices
assert!(items.iter().all(|item| item.node_index < map.graph.node_count()));
}

View File

@@ -1,4 +1,5 @@
use pacman::texture::sprite::{AtlasMapper, MapperFrame, SpriteAtlas};
use glam::U16Vec2;
use pacman::texture::sprite::{AtlasMapper, AtlasTile, MapperFrame, Sprite, SpriteAtlas};
use sdl2::pixels::Color;
use std::collections::HashMap;
@@ -76,3 +77,27 @@ fn test_sprite_atlas_color() {
atlas.set_color(color);
assert_eq!(atlas.default_color(), Some(color));
}
#[test]
fn test_atlas_tile_new_and_with_color() {
let pos = U16Vec2::new(10, 20);
let size = U16Vec2::new(30, 40);
let color = Color::RGB(100, 150, 200);
let tile = AtlasTile::new(pos, size, None);
assert_eq!(tile.pos, pos);
assert_eq!(tile.size, size);
assert_eq!(tile.color, None);
let tile_with_color = tile.with_color(color);
assert_eq!(tile_with_color.color, Some(color));
}
#[test]
fn test_sprite_new() {
let atlas_tile = AtlasTile::new(U16Vec2::new(0, 0), U16Vec2::new(16, 16), None);
let sprite = Sprite::new(atlas_tile);
assert_eq!(sprite.atlas_tile.pos, atlas_tile.pos);
assert_eq!(sprite.atlas_tile.size, atlas_tile.size);
}