176 lines
5.1 KiB
Rust
176 lines
5.1 KiB
Rust
use bevy::ecs::system::RunSystemOnce;
|
|
use pomomon_garden::features::grid::components::{Grid, Tile, TileState};
|
|
use pomomon_garden::features::pom::utils::find_path;
|
|
use pomomon_garden::prelude::*;
|
|
use std::collections::VecDeque;
|
|
|
|
// Helper to set up a Bevy App for pathfinding tests
|
|
fn setup_pathfinding_app(
|
|
grid_width: u32,
|
|
grid_height: u32,
|
|
initial_tile_states: &[(u32, u32, TileState)], // (x, y, state)
|
|
) -> App {
|
|
let mut app = App::new();
|
|
app.add_plugins(MinimalPlugins);
|
|
|
|
let mut grid_tiles = Vec::with_capacity(grid_width as usize);
|
|
for x in 0..grid_width {
|
|
let mut column = Vec::with_capacity(grid_height as usize);
|
|
for y in 0..grid_height {
|
|
let entity = app
|
|
.world_mut()
|
|
.spawn((Tile { x, y }, TileState::Unclaimed))
|
|
.id();
|
|
column.push(entity);
|
|
}
|
|
grid_tiles.push(column);
|
|
}
|
|
app.world_mut().insert_resource(Grid {
|
|
width: grid_width,
|
|
height: grid_height,
|
|
tiles: grid_tiles,
|
|
});
|
|
|
|
for &(x, y, ref state) in initial_tile_states {
|
|
if let Ok(entity) = app.world().resource::<Grid>().get_tile((x, y)) {
|
|
*app.world_mut().get_mut::<TileState>(entity).unwrap() = state.clone();
|
|
}
|
|
}
|
|
|
|
app
|
|
}
|
|
|
|
// Struct to hold start and end positions as a Resource
|
|
#[derive(Resource)]
|
|
struct PathParams {
|
|
start: UVec2,
|
|
end: UVec2,
|
|
}
|
|
|
|
// Test system to run find_path and store result
|
|
#[derive(Resource, Default)]
|
|
struct PathResult(Option<VecDeque<(u32, u32)>>);
|
|
|
|
fn pathfinding_system(
|
|
grid: Res<Grid>,
|
|
tile_query: Query<&TileState>,
|
|
mut path_result: ResMut<PathResult>,
|
|
path_params: Res<PathParams>, // Input: (start, end)
|
|
) {
|
|
path_result.0 = find_path(
|
|
path_params.start.into(),
|
|
path_params.end.into(),
|
|
&grid,
|
|
&tile_query,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_find_path_simple() {
|
|
let mut app = setup_pathfinding_app(5, 5, &[]); // Empty 5x5 grid
|
|
|
|
app.world_mut().insert_resource(PathResult(None));
|
|
app.world_mut().insert_resource(PathParams {
|
|
start: UVec2::new(0, 0),
|
|
end: UVec2::new(4, 4),
|
|
});
|
|
|
|
let _ = app.world_mut().run_system_once(pathfinding_system);
|
|
|
|
let path_result = app.world().resource::<PathResult>();
|
|
assert!(path_result.0.is_some());
|
|
let path = path_result.0.as_ref().unwrap();
|
|
// A* with Manhattan distance will find a shortest path.
|
|
// Length for (0,0) to (4,4) in an empty grid is 8 steps + 1 start = 9 nodes
|
|
assert_eq!(path.len(), 9, "Path length incorrect for simple path");
|
|
assert_eq!(*path.front().unwrap(), (0, 0), "Path should start at (0,0)");
|
|
assert_eq!(*path.back().unwrap(), (4, 4), "Path should end at (4,4)");
|
|
}
|
|
|
|
#[test]
|
|
fn test_find_path_around_obstacle() {
|
|
let obstacle: TileState = TileState::Occupied {
|
|
seed: ItemType::BerrySeed {
|
|
name: "Test".into(),
|
|
},
|
|
watered: false,
|
|
growth_stage: 0,
|
|
withered: false,
|
|
dry_counter: 0,
|
|
};
|
|
let obstacles = vec![
|
|
(2, 2, obstacle.clone()),
|
|
(2, 3, obstacle.clone()),
|
|
(2, 4, obstacle.clone()),
|
|
];
|
|
let mut app = setup_pathfinding_app(5, 5, &obstacles);
|
|
|
|
let _ = app.world_mut().insert_resource(PathResult(None));
|
|
let _ = app.world_mut().insert_resource(PathParams {
|
|
start: UVec2::new(0, 0),
|
|
end: UVec2::new(4, 4),
|
|
});
|
|
|
|
let _ = app.world_mut().run_system_once(pathfinding_system);
|
|
|
|
let path_result = app.world().resource::<PathResult>();
|
|
|
|
if path_result.0.is_none() {
|
|
panic!("Should find a path around obstacles, but found none.");
|
|
}
|
|
|
|
let path = path_result.0.as_ref().unwrap();
|
|
|
|
assert_eq!(*path.front().unwrap(), (0, 0));
|
|
assert_eq!(*path.back().unwrap(), (4, 4));
|
|
|
|
// Assert that no obstacle tile is in the path
|
|
for (ox, oy, _) in &obstacles {
|
|
assert!(
|
|
!path.contains(&(*ox, *oy)),
|
|
"Path should not contain obstacle {:?}",
|
|
(ox, oy)
|
|
);
|
|
}
|
|
|
|
// The shortest path around these specific obstacles has a length of 9 nodes (8 steps)
|
|
// For example: (0,0) -> (1,0) -> (2,0) -> (3,0) -> (4,0) -> (4,1) -> (4,2) -> (4,3) -> (4,4)
|
|
assert_eq!(
|
|
path.len(),
|
|
9,
|
|
"Path length incorrect for path around obstacles."
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_find_path_no_path() {
|
|
let obstacle: TileState = TileState::Occupied {
|
|
seed: ItemType::BerrySeed {
|
|
name: "Test".into(),
|
|
},
|
|
watered: false,
|
|
growth_stage: 0,
|
|
withered: false,
|
|
dry_counter: 0,
|
|
};
|
|
let obstacles = vec![
|
|
(2, 0, obstacle.clone()),
|
|
(2, 1, obstacle.clone()),
|
|
(2, 2, obstacle.clone()),
|
|
(2, 3, obstacle.clone()),
|
|
(2, 4, obstacle.clone()),
|
|
];
|
|
let mut app = setup_pathfinding_app(5, 5, &obstacles);
|
|
|
|
app.world_mut().insert_resource(PathResult(None));
|
|
app.world_mut().insert_resource(PathParams {
|
|
start: UVec2::new(0, 0),
|
|
end: UVec2::new(4, 4),
|
|
});
|
|
|
|
let _ = app.world_mut().run_system_once(pathfinding_system);
|
|
|
|
let path_result = app.world().resource::<PathResult>();
|
|
assert!(path_result.0.is_none(), "Expected no path when blocked");
|
|
}
|