mirror of
https://github.com/Xevion/smart-rgb.git
synced 2025-12-06 01:16:24 -06:00
234 lines
8.8 KiB
Rust
234 lines
8.8 KiB
Rust
/// 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 }
|
|
}
|
|
}
|