From 183a43211636af3b7f685f74b42276d35bfd1738 Mon Sep 17 00:00:00 2001 From: Xevion Date: Tue, 12 Aug 2025 00:44:19 -0500 Subject: [PATCH] test: add tests for collision, items, directional, sprite enum macros for FruitKind --- Cargo.lock | 26 ++++++++++ Cargo.toml | 2 + src/entity/item.rs | 16 +++++- tests/collision.rs | 119 +++++++++++++++++++++++++++++++++++++++++++ tests/directional.rs | 23 +++++++++ tests/game.rs | 3 ++ tests/item.rs | 53 +++++++++++++++++++ tests/sprite.rs | 27 +++++++++- 8 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 tests/collision.rs create mode 100644 tests/item.rs diff --git a/Cargo.lock b/Cargo.lock index b2de53f..0bb6b18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 5a393b1..165f1d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 diff --git a/src/entity/item.rs b/src/entity/item.rs index 783b89e..d8cc31c 100644 --- a/src/entity/item.rs +++ b/src/entity/item.rs @@ -5,6 +5,7 @@ use crate::{ 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, diff --git a/tests/collision.rs b/tests/collision.rs new file mode 100644 index 0000000..a7ea569 --- /dev/null +++ b/tests/collision.rs @@ -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]); +} diff --git a/tests/directional.rs b/tests/directional.rs index e9b4708..11b3707 100644 --- a/tests/directional.rs +++ b/tests/directional.rs @@ -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); +} diff --git a/tests/game.rs b/tests/game.rs index 246267f..c5e41c6 100644 --- a/tests/game.rs +++ b/tests/game.rs @@ -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(); diff --git a/tests/item.rs b/tests/item.rs new file mode 100644 index 0000000..01598fe --- /dev/null +++ b/tests/item.rs @@ -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::>(); + 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()); +} diff --git a/tests/sprite.rs b/tests/sprite.rs index 52efd99..9da3510 100644 --- a/tests/sprite.rs +++ b/tests/sprite.rs @@ -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); +}