diff --git a/src/inventory/item.rs b/src/inventory/item.rs index 6c32889..211d612 100644 --- a/src/inventory/item.rs +++ b/src/inventory/item.rs @@ -1,3 +1,5 @@ +use std::mem::swap; + use bevy::prelude::*; #[derive(Component, Clone)] @@ -32,4 +34,16 @@ impl Item { !rect.intersect(other_rect).is_empty() } + + /// Swap size.x with size.y + pub fn swap_size(&mut self) { + swap(&mut self.size.x, &mut self.size.y); + } + + /// Get clone of item with swapped size + pub fn clone_swapped(&self) -> Self { + let mut new = self.clone(); + new.swap_size(); + new + } } diff --git a/src/inventory/mod.rs b/src/inventory/mod.rs index 62af730..d070aaf 100644 --- a/src/inventory/mod.rs +++ b/src/inventory/mod.rs @@ -53,4 +53,39 @@ impl Inventory { .collect::>(); self.can_fit(item_query, children.as_slice(), *size, queried_position) } + + fn find_free_space_inner( + &self, + item_query: Query<&item::Item>, + contained_items: &[Entity], + queried_size: UVec2, + was_swapped: bool, + ) -> Option<(UVec2, bool)> { + let Some(UVec2 {x: tries_x, y: tries_y}) = self.size.checked_sub(queried_size) else { + return None; + }; + for x in 0..=tries_x { + for y in 0..=tries_y { + let tested_pos = UVec2::new(x, y); + if self.can_fit(item_query, contained_items, queried_size, tested_pos) { + return Some((tested_pos, was_swapped)); + } + } + } + None + } + + pub fn find_free_space( + &self, + item_query: Query<&item::Item>, + contained_items: &[Entity], + queried_size: UVec2, + ) -> Option<(UVec2, bool)> { + let result = self.find_free_space_inner(item_query, contained_items, queried_size, false); + if result.is_some() { + return result; + } + let swapped_size = UVec2::new(queried_size.y, queried_size.x); + self.find_free_space_inner(item_query, contained_items, swapped_size, true) + } } diff --git a/src/tests.rs b/src/tests.rs index 19c7b0a..7222408 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -84,30 +84,37 @@ mod inventory { Inventory::new(UVec2::splat(4)) } + /// 1x2 0;0 fn item_a() -> Item { Item::new_positioned(UVec2::new(1, 2), UVec2::new(0, 0)) } + /// 2x2 0;2 fn item_b() -> Item { Item::new_positioned(UVec2::new(2, 2), UVec2::new(0, 2)) } + /// 3x2 1;0 fn item_c() -> Item { Item::new_positioned(UVec2::new(3, 2), UVec2::new(1, 0)) } + /// 2x2 2;2 fn item_d() -> Item { Item::new_positioned(UVec2::new(2, 2), UVec2::new(2, 2)) } + /// 1x1 0;0 fn item_e() -> Item { Item::new_positioned(UVec2::new(1, 1), UVec2::new(0, 0)) } + /// 5x5 0;0 fn item_f() -> Item { Item::new_positioned(UVec2::new(5, 5), UVec2::new(0, 0)) } + /// 2x2 3;3 fn item_g() -> Item { Item::new_positioned(UVec2::new(2, 2), UVec2::new(3, 3)) } @@ -115,7 +122,7 @@ mod inventory { #[derive(Resource)] struct Items(Vec, usize, bool); - #[derive(Component)] + #[derive(Component, Resource)] struct MovableItem(UVec2, bool); fn insert_item( @@ -154,6 +161,20 @@ mod inventory { } } + fn find_space( + item_query: Query<&Item>, + inventory_query: Query<(&Inventory, &Children)>, + movable_item: Res, + ) { + let (inventory, children) = inventory_query.single().unwrap(); + let MovableItem(query_size, assertion) = *movable_item; + if assertion { + assert!(inventory.find_free_space(item_query, children, query_size).is_some()); + } else { + assert!(inventory.find_free_space(item_query, children, query_size).is_none()); + } + } + #[test] fn everything_fits() { let mut world = World::new(); @@ -230,4 +251,53 @@ mod inventory { world.run_system(system).expect("Error on running system"); } + + #[test] + fn should_find_space_for_item() { + let mut world = World::new(); + + let system = world.register_system(find_space); + + world.insert_resource(MovableItem(UVec2::new(2, 2), true)); + + world.spawn(inventory()) + .with_child(item_a()) + .with_child(item_b()) + .with_child(item_c()); + + world.run_system(system).expect("Error on running system"); + } + + #[test] + fn should_not_find_space_for_item() { + let mut world = World::new(); + + let system = world.register_system(find_space); + + world.insert_resource(MovableItem(UVec2::new(2, 2), false)); + + world.spawn(inventory()) + .with_child(item_a()) + .with_child(item_b()) + .with_child(item_c()) + .with_child(item_d()); + + world.run_system(system).expect("Error on running system"); + } + + #[test] + fn item_fits_if_rotated() { + let mut world = World::new(); + + let system = world.register_system(find_space); + + world.insert_resource(MovableItem(UVec2::new(2, 3), true)); + + world.spawn(inventory()) + .with_child(item_a()) + .with_child(item_b()) + .with_child(item_d()); + + world.run_system(system).expect("Error on running system"); + } }