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::().get_tile((x, y)) { *app.world_mut().get_mut::(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>); fn pathfinding_system( grid: Res, tile_query: Query<&TileState>, mut path_result: ResMut, path_params: Res, // 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::(); 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::(); 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::(); assert!(path_result.0.is_none(), "Expected no path when blocked"); }