mirror of
https://github.com/Xevion/smart-rgb.git
synced 2025-12-15 00:13:12 -06:00
Update source files
This commit is contained in:
233
crates/borders-core/tests/common/builders.rs
Normal file
233
crates/borders-core/tests/common/builders.rs
Normal file
@@ -0,0 +1,233 @@
|
||||
/// Test builders for creating Bevy World and game state
|
||||
///
|
||||
/// Provides fluent API for constructing test environments with minimal boilerplate.
|
||||
use borders_core::prelude::*;
|
||||
|
||||
pub struct TestWorld {
|
||||
map_width: u16,
|
||||
map_height: u16,
|
||||
players: Vec<(NationId, f32, Option<String>)>, // (id, troops, name)
|
||||
territories: Vec<(NationId, Vec<U16Vec2>)>,
|
||||
terrain: Option<TerrainData>,
|
||||
spawn_phase_active: bool,
|
||||
rng_seed: u64,
|
||||
}
|
||||
|
||||
impl TestWorld {
|
||||
/// Create a new test world builder with default settings
|
||||
pub fn new() -> Self {
|
||||
Self { map_width: 100, map_height: 100, players: Vec::new(), territories: Vec::new(), terrain: None, spawn_phase_active: false, rng_seed: 0xDEADBEEF }
|
||||
}
|
||||
|
||||
/// Set the map size
|
||||
pub fn with_map_size(mut self, width: u16, height: u16) -> Self {
|
||||
self.map_width = width;
|
||||
self.map_height = height;
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a player with specified troops
|
||||
pub fn with_player(mut self, id: NationId, troops: f32) -> Self {
|
||||
self.players.push((id, troops, None));
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a named player
|
||||
pub fn with_named_player(mut self, id: NationId, troops: f32, name: String) -> Self {
|
||||
self.players.push((id, troops, Some(name)));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set territory ownership for a player
|
||||
pub fn with_territory(mut self, id: NationId, tiles: &[U16Vec2]) -> Self {
|
||||
self.territories.push((id, tiles.to_vec()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Provide custom terrain data
|
||||
pub fn with_terrain(mut self, terrain: TerrainData) -> Self {
|
||||
self.terrain = Some(terrain);
|
||||
self
|
||||
}
|
||||
|
||||
/// Activate spawn phase
|
||||
pub fn with_spawn_phase(mut self) -> Self {
|
||||
self.spawn_phase_active = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set RNG seed for deterministic tests
|
||||
pub fn with_rng_seed(mut self, seed: u64) -> Self {
|
||||
self.rng_seed = seed;
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the World with all configured state
|
||||
pub fn build(self) -> World {
|
||||
let mut world = World::new();
|
||||
|
||||
// Initialize Time resources (required by many systems)
|
||||
world.insert_resource(Time::default());
|
||||
world.insert_resource(FixedTime::from_seconds(0.1));
|
||||
|
||||
// Generate or use provided terrain
|
||||
let terrain = self.terrain.unwrap_or_else(|| MapBuilder::new(self.map_width, self.map_height).all_conquerable().build());
|
||||
|
||||
// Initialize TerritoryManager
|
||||
let mut territory_manager = TerritoryManager::new(self.map_width, self.map_height);
|
||||
let conquerable_tiles: Vec<bool> = {
|
||||
let mut tiles = Vec::with_capacity((self.map_width as usize) * (self.map_height as usize));
|
||||
for y in 0..self.map_height {
|
||||
for x in 0..self.map_width {
|
||||
tiles.push(terrain.is_conquerable(U16Vec2::new(x, y)));
|
||||
}
|
||||
}
|
||||
tiles
|
||||
};
|
||||
territory_manager.reset(self.map_width, self.map_height, &conquerable_tiles);
|
||||
|
||||
// Apply territory ownership
|
||||
for (nation_id, tiles) in &self.territories {
|
||||
for &tile in tiles {
|
||||
territory_manager.conquer(tile, *nation_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Create player entities
|
||||
let mut entity_map = PlayerEntityMap::default();
|
||||
for (i, (nation_id, troops, name)) in self.players.iter().enumerate() {
|
||||
let player_name = name.clone().unwrap_or_else(|| format!("Player {}", nation_id.get()));
|
||||
|
||||
let color = HSLColor::new(
|
||||
(i as f32 * 137.5) % 360.0, // golden angle distribution
|
||||
0.6,
|
||||
0.5,
|
||||
);
|
||||
|
||||
let entity = world.spawn((*nation_id, PlayerName(player_name), PlayerColor(color), BorderTiles::default(), Troops(*troops), TerritorySize(0), borders_core::game::ships::ShipCount::default())).id();
|
||||
|
||||
entity_map.0.insert(*nation_id, entity);
|
||||
}
|
||||
|
||||
// Insert core resources
|
||||
world.insert_resource(entity_map);
|
||||
world.insert_resource(territory_manager);
|
||||
world.insert_resource(ActiveAttacks::new());
|
||||
world.insert_resource(terrain);
|
||||
world.insert_resource(DeterministicRng::new(self.rng_seed));
|
||||
world.insert_resource(BorderCache::default());
|
||||
world.insert_resource(AttackControls::default());
|
||||
world.insert_resource(ClientPlayerId(NationId::ZERO));
|
||||
world.insert_resource(HumanPlayerCount(1));
|
||||
world.insert_resource(LocalPlayerContext::new(NationId::ZERO));
|
||||
|
||||
// Optional spawn phase
|
||||
if self.spawn_phase_active {
|
||||
world.insert_resource(SpawnPhase { active: true });
|
||||
world.insert_resource(SpawnTimeout::new(30.0));
|
||||
}
|
||||
|
||||
// Compute coastal tiles
|
||||
let map_size = U16Vec2::new(self.map_width, self.map_height);
|
||||
let coastal_tiles = CoastalTiles::compute(world.resource::<TerrainData>(), map_size);
|
||||
world.insert_resource(coastal_tiles);
|
||||
|
||||
world
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TestWorld {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for programmatic terrain generation
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// let terrain = MapBuilder::new(100, 100)
|
||||
/// .all_conquerable()
|
||||
/// .build();
|
||||
/// ```
|
||||
pub struct MapBuilder {
|
||||
width: u16,
|
||||
height: u16,
|
||||
terrain_data: Vec<u8>,
|
||||
tile_types: Vec<TileType>,
|
||||
}
|
||||
|
||||
impl MapBuilder {
|
||||
/// Create a new map builder
|
||||
pub fn new(width: u16, height: u16) -> Self {
|
||||
let size = (width as usize) * (height as usize);
|
||||
Self { width, height, terrain_data: vec![0; size], tile_types: Vec::new() }
|
||||
}
|
||||
|
||||
/// Make all tiles land and conquerable
|
||||
pub fn all_conquerable(mut self) -> Self {
|
||||
let size = (self.width as usize) * (self.height as usize);
|
||||
self.terrain_data = vec![0x80; size]; // bit 7 = land/conquerable
|
||||
|
||||
self.tile_types = vec![TileType { name: "water".to_string(), color_base: "blue".to_string(), color_variant: 0, conquerable: false, navigable: true, expansion_time: 255, expansion_cost: 255 }, TileType { name: "land".to_string(), color_base: "green".to_string(), color_variant: 0, conquerable: true, navigable: false, expansion_time: 50, expansion_cost: 50 }];
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Create islands: center 50x50 land, rest water
|
||||
pub fn islands(mut self) -> Self {
|
||||
self.tile_types = vec![TileType { name: "water".to_string(), color_base: "blue".to_string(), color_variant: 0, conquerable: false, navigable: true, expansion_time: 255, expansion_cost: 255 }, TileType { name: "land".to_string(), color_base: "green".to_string(), color_variant: 0, conquerable: true, navigable: false, expansion_time: 50, expansion_cost: 50 }];
|
||||
|
||||
let center_x = self.width / 2;
|
||||
let center_y = self.height / 2;
|
||||
let island_size = 25u16;
|
||||
|
||||
for y in 0..self.height {
|
||||
for x in 0..self.width {
|
||||
let idx = (y as usize) * (self.width as usize) + (x as usize);
|
||||
let in_island = x.abs_diff(center_x) < island_size && y.abs_diff(center_y) < island_size;
|
||||
self.terrain_data[idx] = if in_island { 0x80 } else { 0x00 };
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Create continents: alternating vertical strips of land/water
|
||||
pub fn continents(mut self) -> Self {
|
||||
self.tile_types = vec![TileType { name: "water".to_string(), color_base: "blue".to_string(), color_variant: 0, conquerable: false, navigable: true, expansion_time: 255, expansion_cost: 255 }, TileType { name: "land".to_string(), color_base: "green".to_string(), color_variant: 0, conquerable: true, navigable: false, expansion_time: 50, expansion_cost: 50 }];
|
||||
|
||||
for y in 0..self.height {
|
||||
for x in 0..self.width {
|
||||
let idx = (y as usize) * (self.width as usize) + (x as usize);
|
||||
// 30-tile wide strips
|
||||
let is_land = (x / 30) % 2 == 0;
|
||||
self.terrain_data[idx] = if is_land { 0x80 } else { 0x00 };
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Set specific tiles as land
|
||||
pub fn with_land(mut self, tiles: &[U16Vec2]) -> Self {
|
||||
for &tile in tiles {
|
||||
let idx = (tile.y as usize) * (self.width as usize) + (tile.x as usize);
|
||||
if idx < self.terrain_data.len() {
|
||||
self.terrain_data[idx] = 0x80;
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the TerrainData
|
||||
pub fn build(self) -> TerrainData {
|
||||
let tiles: Vec<u8> = self.terrain_data.iter().map(|&byte| if byte & 0x80 != 0 { 1 } else { 0 }).collect();
|
||||
|
||||
let num_land_tiles = tiles.iter().filter(|&&t| t == 1).count();
|
||||
|
||||
let terrain_tile_map = TileMap::from_vec(self.width, self.height, self.terrain_data);
|
||||
|
||||
TerrainData { _manifest: MapManifest { map: MapMetadata { size: U16Vec2::new(self.width, self.height), num_land_tiles }, name: "Test Map".to_string(), nations: Vec::new() }, terrain_data: terrain_tile_map, tiles, tile_types: self.tile_types }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user