#![allow(dead_code)] #![allow(unused_imports)] /// Shared test utilities and helpers /// /// This module provides infrastructure for testing the Bevy ECS-based game logic: /// - `builders`: Fluent API for constructing test worlds and maps /// - `assertions`: Fluent assertion API for ECS state verification /// - `fixtures`: Pre-built test scenarios for integration tests /// /// # Usage Example /// ``` /// use common::builders::TestWorld; /// /// let mut world = TestWorld::new() /// .with_player(NationId::ZERO, 100.0) /// .with_map_size(50, 50) /// .build(); /// /// world.conquer_tile(U16Vec2::new(5, 5), NationId::ZERO); /// world.assert().player_owns(U16Vec2::new(5, 5), NationId::ZERO); /// ``` use borders_core::prelude::*; use extension_traits::extension; use std::collections::HashMap; use std::collections::HashSet; mod assertions; mod builders; mod fixtures; // Re-export commonly used items pub use assertions::*; pub use builders::*; pub use fixtures::*; /// Extension trait providing convenient action methods for World in tests #[extension(pub trait WorldTestExt)] impl World { /// Conquer a tile for a player fn conquer_tile(&mut self, tile: U16Vec2, player: NationId) { self.resource_mut::().conquer(tile, player); } /// Conquer multiple tiles for a player fn conquer_tiles(&mut self, tiles: &[U16Vec2], player: NationId) { let mut territory_manager = self.resource_mut::(); for &tile in tiles { territory_manager.conquer(tile, player); } } /// Conquer a square region of tiles centered at a position /// /// Conquers all tiles within `radius` steps of `center` (using taxicab distance). /// For radius=1, conquers a 3x3 square. For radius=2, conquers a 5x5 square, etc. fn conquer_region(&mut self, center: U16Vec2, radius: i32, player: NationId) { let mut territory_manager = self.resource_mut::(); for dy in -radius..=radius { for dx in -radius..=radius { let tile = center.as_ivec2() + glam::IVec2::new(dx, dy); if tile.x >= 0 && tile.y >= 0 { let tile = tile.as_u16vec2(); if tile.x < territory_manager.width() && tile.y < territory_manager.height() { territory_manager.conquer(tile, player); } } } } } /// Conquer all 4-directional neighbors of a tile fn conquer_neighbors(&mut self, center: U16Vec2, player: NationId) { let map_size = { let mgr = self.resource::(); U16Vec2::new(mgr.width(), mgr.height()) }; let mut territory_manager = self.resource_mut::(); for neighbor in neighbors(center, map_size) { territory_manager.conquer(neighbor, player); } } /// Clear ownership of a tile, returning the previous owner fn clear_tile(&mut self, tile: U16Vec2) -> Option { self.resource_mut::().clear(tile) } fn est(&mut self) {} /// Clear all territory changes from the change buffer fn clear_territory_changes(&mut self) { self.resource_mut::().clear_changes(); } /// Deactivate the spawn phase fn deactivate_spawn_phase(&mut self) { self.resource_mut::().active = false; } /// Activate the spawn phase fn activate_spawn_phase(&mut self) { self.resource_mut::().active = true; } /// Get the number of territory changes in the ChangeBuffer fn get_change_count(&self) -> usize { self.resource::().iter_changes().count() } /// Get the entity associated with a player fn get_player_entity(&self, player_id: NationId) -> Entity { let entity_map = self.resource::(); *entity_map.0.get(&player_id).unwrap_or_else(|| panic!("Player entity not found for player {}", player_id.get())) } /// Run the border update logic (inline implementation for testing) fn update_borders(&mut self) { // Check if there are changes { let territory_manager = self.resource::(); if !territory_manager.has_changes() { return; } } // Collect all data we need from TerritoryManager before mutating let (changed_tiles, map_size, tiles_by_owner) = { let territory_manager = self.resource::(); let changed_tiles: HashSet = territory_manager.iter_changes().collect(); let map_size = U16Vec2::new(territory_manager.width(), territory_manager.height()); let mut affected_tiles = HashSet::with_capacity(changed_tiles.len() * 5); for &tile in &changed_tiles { affected_tiles.insert(tile); affected_tiles.extend(neighbors(tile, map_size)); } // Group tiles by owner let mut tiles_by_owner: HashMap> = HashMap::new(); for &tile in &affected_tiles { if let Some(nation_id) = territory_manager.get_nation_id(tile) { tiles_by_owner.entry(nation_id).or_default().insert(tile); } } (changed_tiles, map_size, tiles_by_owner) }; // Build ownership snapshot for border checking let ownership_snapshot: HashMap> = { let territory_manager = self.resource::(); let mut snapshot = HashMap::new(); for &tile in changed_tiles.iter() { for neighbor in neighbors(tile, map_size) { snapshot.entry(neighbor).or_insert_with(|| territory_manager.get_nation_id(neighbor)); } snapshot.insert(tile, territory_manager.get_nation_id(tile)); } snapshot }; // Update each player's borders let mut players_query = self.query::<(&NationId, &mut BorderTiles)>(); for (nation_id, mut component_borders) in players_query.iter_mut(self) { let empty_set = HashSet::new(); let player_tiles = tiles_by_owner.get(nation_id).unwrap_or(&empty_set); // Process tiles owned by this player for &tile in player_tiles { let is_border = neighbors(tile, map_size).any(|neighbor| ownership_snapshot.get(&neighbor).and_then(|&owner| owner) != Some(*nation_id)); if is_border { component_borders.0.insert(tile); } else { component_borders.0.remove(&tile); } } // Remove tiles that changed ownership away from this player for &tile in changed_tiles.iter() { if ownership_snapshot.get(&tile).and_then(|&owner| owner) != Some(*nation_id) { component_borders.0.remove(&tile); } } } } /// Clear territory changes fn clear_borders_changes(&mut self) { self.resource_mut::().clear_changes(); } /// Get border tiles for a player from ECS component fn get_player_borders(&self, player_id: NationId) -> HashSet { let entity = self.get_player_entity(player_id); self.get::(entity).expect("Player entity missing BorderTiles component").0.clone() } /// Get border tiles for a player from BorderCache fn get_border_cache(&self, player_id: NationId) -> Option> { self.resource::().get(player_id).cloned() } }