Merge branch '67-pom-interaction-trap-prevention' into 'dev'
Implement Pom Interaction Trapping Prevention See merge request softwaregrundprojekt/2025-2026/einzelprojekt/tutorium-moritz/bernroider-dominik/bernroider-dominik!39
This commit is contained in:
@@ -184,7 +184,7 @@ fn handle_interact(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_interaction(
|
pub fn perform_interaction(
|
||||||
mut pom_query: Query<(&GridPosition, &mut InteractionTarget, &PathQueue)>,
|
mut pom_query: Query<(&GridPosition, &mut InteractionTarget, &PathQueue)>,
|
||||||
grid: Res<Grid>,
|
grid: Res<Grid>,
|
||||||
mut tile_query: Query<&mut TileState>,
|
mut tile_query: Query<&mut TileState>,
|
||||||
@@ -193,6 +193,7 @@ fn perform_interaction(
|
|||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
config: Res<GameConfig>,
|
config: Res<GameConfig>,
|
||||||
mut session_tracker: ResMut<crate::features::phase::components::SessionTracker>,
|
mut session_tracker: ResMut<crate::features::phase::components::SessionTracker>,
|
||||||
|
mut notifications: ResMut<Notifications>,
|
||||||
) {
|
) {
|
||||||
for (pos, mut target_component, path_queue) in pom_query.iter_mut() {
|
for (pos, mut target_component, path_queue) in pom_query.iter_mut() {
|
||||||
if let Some(target) = target_component.target {
|
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 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::<String>, "That would trap you!");
|
||||||
|
target_component.target = None;
|
||||||
|
target_component.action = None;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"Performing interaction on tile ({}, {})",
|
"Performing interaction on tile ({}, {})",
|
||||||
target.0, target.1
|
target.0, target.1
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::{BinaryHeap, HashMap, VecDeque};
|
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
pub struct Node {
|
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)
|
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<F>(
|
||||||
|
start: (u32, u32),
|
||||||
|
blocked_pos: (u32, u32),
|
||||||
|
grid: &Grid,
|
||||||
|
get_tile_state: F,
|
||||||
|
) -> bool
|
||||||
|
where
|
||||||
|
F: Fn(Entity) -> Option<TileState>,
|
||||||
|
{
|
||||||
|
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(
|
pub fn find_path(
|
||||||
start: (u32, u32),
|
start: (u32, u32),
|
||||||
end: (u32, u32),
|
end: (u32, u32),
|
||||||
|
|||||||
109
tests/trapped.rs
Normal file
109
tests/trapped.rs
Normal file
@@ -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::<Notifications>();
|
||||||
|
|
||||||
|
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::<Grid>();
|
||||||
|
let tile_entity = grid.get_tile((1, 0)).unwrap();
|
||||||
|
let tile_state = app.world().entity(tile_entity).get::<TileState>().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::<Notifications>();
|
||||||
|
|
||||||
|
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::<Grid>();
|
||||||
|
let tile_entity = grid.get_tile((1, 0)).unwrap();
|
||||||
|
let tile_state = app.world().entity(tile_entity).get::<TileState>().unwrap();
|
||||||
|
|
||||||
|
// Should be Occupied
|
||||||
|
match tile_state {
|
||||||
|
TileState::Occupied { .. } => (),
|
||||||
|
_ => panic!("Should have allowed planting! State is {:?}", tile_state),
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user