diff --git a/src/features/pom/mod.rs b/src/features/pom/mod.rs index c8dffb4..ed55560 100644 --- a/src/features/pom/mod.rs +++ b/src/features/pom/mod.rs @@ -184,7 +184,7 @@ fn handle_interact( } } -fn perform_interaction( +pub fn perform_interaction( mut pom_query: Query<(&GridPosition, &mut InteractionTarget, &PathQueue)>, grid: Res, mut tile_query: Query<&mut TileState>, @@ -193,6 +193,7 @@ fn perform_interaction( mut commands: Commands, config: Res, mut session_tracker: ResMut, + mut notifications: ResMut, ) { for (pos, mut target_component, path_queue) in pom_query.iter_mut() { if let Some(target) = target_component.target { @@ -202,6 +203,17 @@ fn perform_interaction( } if manhattan_distance(pos.x, pos.y, target.0, target.1) == 1 { + if let Some(actions::InteractionAction::Plant(_)) = &target_component.action { + if utils::is_trapped((pos.x, pos.y), target, &grid, |e| { + tile_query.get(e).ok().cloned() + }) { + notifications.error(None::, "That would trap you!"); + target_component.target = None; + target_component.action = None; + continue; + } + } + println!( "Performing interaction on tile ({}, {})", target.0, target.1 diff --git a/src/features/pom/utils.rs b/src/features/pom/utils.rs index 73ad8fb..0658684 100644 --- a/src/features/pom/utils.rs +++ b/src/features/pom/utils.rs @@ -1,6 +1,6 @@ use crate::prelude::*; use std::cmp::Ordering; -use std::collections::{BinaryHeap, HashMap, VecDeque}; +use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque}; #[derive(Copy, Clone, Eq, PartialEq)] pub struct Node { @@ -29,6 +29,75 @@ pub fn manhattan_distance(x1: u32, y1: u32, x2: u32, y2: u32) -> u32 { x1.abs_diff(x2) + y1.abs_diff(y2) } +/// Checks if Pom would be trapped in a small, isolated area if a specific tile is blocked +pub fn is_trapped( + start: (u32, u32), + blocked_pos: (u32, u32), + grid: &Grid, + get_tile_state: F, +) -> bool +where + F: Fn(Entity) -> Option, +{ + let mut open_set = VecDeque::new(); + let mut visited = HashSet::new(); + + open_set.push_back(start); + visited.insert(start); + + if start == blocked_pos { + return true; + } + + let max_search = 50; + let min_safe_area = 5; + + while let Some(current) = open_set.pop_front() { + if visited.len() >= max_search { + return false; + } + + let neighbors = [ + (current.0 as i32 + 1, current.1 as i32), + (current.0 as i32 - 1, current.1 as i32), + (current.0 as i32, current.1 as i32 + 1), + (current.0 as i32, current.1 as i32 - 1), + ]; + + for (nx, ny) in neighbors { + if nx < 0 || ny < 0 { + continue; + } + let next_pos = (nx as u32, ny as u32); + + if next_pos == blocked_pos { + continue; + } + + if visited.contains(&next_pos) { + continue; + } + + let tile_entity = match grid.get_tile(next_pos) { + Ok(e) => e, + Err(_) => continue, + }; + + if let Some(state) = get_tile_state(tile_entity) { + if let TileState::Unclaimed = state { + return false; + } + if !state.is_blocking() { + visited.insert(next_pos); + open_set.push_back(next_pos); + } + } + } + } + + visited.len() < min_safe_area +} + pub fn find_path( start: (u32, u32), end: (u32, u32), diff --git a/tests/trapped.rs b/tests/trapped.rs new file mode 100644 index 0000000..c5c38a4 --- /dev/null +++ b/tests/trapped.rs @@ -0,0 +1,109 @@ +use bevy::ecs::system::RunSystemOnce; +use pomomon_garden::features::grid::components::{Grid, TileState}; +use pomomon_garden::features::inventory::components::ItemType; +use pomomon_garden::features::notification::components::Notifications; +use pomomon_garden::features::pom::actions::InteractionAction; +use pomomon_garden::features::pom::components::{GridPosition, InteractionTarget, PathQueue, Pom}; +use pomomon_garden::features::pom::perform_interaction; +use pomomon_garden::prelude::*; + +mod common; +use common::setup_app; + +#[test] +fn test_prevent_trapping_plant() { + let occupied = TileState::Occupied { + seed: ItemType::BerrySeed { + name: "Wall".into(), + }, + watered: false, + growth_stage: 0, + withered: false, + dry_counter: 0, + }; + + let mut app = setup_app( + 2, + 2, + &[ + (0, 0, TileState::Empty), // Pom + (1, 0, TileState::Empty), // Target + (0, 1, occupied.clone()), + (1, 1, occupied.clone()), + ], + vec![( + ItemType::BerrySeed { + name: "Test".into(), + }, + 10, + )], + None, + ); + + app.init_resource::(); + + app.world_mut().spawn(( + Pom, + GridPosition { x: 0, y: 0 }, + InteractionTarget { + target: Some((1, 0)), + action: Some(InteractionAction::Plant(ItemType::BerrySeed { + name: "Test".into(), + })), + }, + PathQueue::default(), + )); + + let _ = app.world_mut().run_system_once(perform_interaction); + + let grid = app.world().resource::(); + let tile_entity = grid.get_tile((1, 0)).unwrap(); + let tile_state = app.world().entity(tile_entity).get::().unwrap(); + + // Should remain Empty because planting was blocked + match tile_state { + TileState::Empty => (), + _ => panic!("Should have prevented planting! State is {:?}", tile_state), + } +} + +#[test] +fn test_allow_safe_plant() { + let mut app = setup_app( + 10, + 10, + &[(0, 0, TileState::Empty), (1, 0, TileState::Empty)], + vec![( + ItemType::BerrySeed { + name: "Test".into(), + }, + 10, + )], + None, + ); + app.init_resource::(); + + app.world_mut().spawn(( + Pom, + GridPosition { x: 0, y: 0 }, + InteractionTarget { + target: Some((1, 0)), + action: Some(InteractionAction::Plant(ItemType::BerrySeed { + name: "Test".into(), + })), + }, + PathQueue::default(), + )); + + let _ = app.world_mut().run_system_once(perform_interaction); + + let grid = app.world().resource::(); + let tile_entity = grid.get_tile((1, 0)).unwrap(); + let tile_state = app.world().entity(tile_entity).get::().unwrap(); + + // Should be Occupied + match tile_state { + TileState::Occupied { .. } => (), + _ => panic!("Should have allowed planting! State is {:?}", tile_state), + } +}