From 34e14900ebcb1df694b3328530b69e4f78b074c1 Mon Sep 17 00:00:00 2001 From: hheik <4469778+hheik@users.noreply.github.com> Date: Tue, 29 Jul 2025 17:26:02 +0300 Subject: [PATCH] Added simple world generation for development --- Cargo.toml | 2 +- defs/sites.def | 10 ++- src/game.rs | 2 +- src/main.rs | 1 - src/sim/creature.rs | 6 +- src/sim/definitions/site.rs | 1 + src/sim/site.rs | 30 +++++---- src/sim/travel_group.rs | 6 +- src/sim/util.rs | 56 +++++++++++++++- src/sim/world.rs | 70 ++++++++++++++++---- src/sim_runner.rs | 126 ------------------------------------ 11 files changed, 144 insertions(+), 166 deletions(-) delete mode 100644 src/sim_runner.rs diff --git a/Cargo.toml b/Cargo.toml index 0e3205a..bb8f504 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "adventure_sim" version = "0.1.0" -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/defs/sites.def b/defs/sites.def index b3eb073..a7d3a6f 100644 --- a/defs/sites.def +++ b/defs/sites.def @@ -1,4 +1,12 @@ [SITE:CAVE] [SIZE:1:3] - # [UNDERGROUND] + [UNDERGROUND] [NATURAL] + +[SITE:DUNGEON] + [SIZE:2:3] + [UNDERGROUND] + +[SITE:BANDIT_CAMP] + [SIZE:1:1] + # [OUTDOORS] diff --git a/src/game.rs b/src/game.rs index 8ecd672..9b22f3b 100644 --- a/src/game.rs +++ b/src/game.rs @@ -16,7 +16,7 @@ pub fn init(app: &mut App) { // .add_systems(Update, systems::demo_2d); let definitions = sim::Definitions::from_default_directory().unwrap(); - let game_world = sim::GameWorld::generate_mock(&definitions); + let game_world = sim::GameWorld::generate_random(&definitions); // let game_world = match std::fs::read(sim::SAVE_FILE) { // Ok(data) => { // info!("Reading world data from \"{}\"", sim::SAVE_FILE); diff --git a/src/main.rs b/src/main.rs index 9d2199e..604724a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,6 @@ pub mod debug; pub mod game; pub mod game_setup; pub mod sim; -pub mod sim_runner; pub mod util; fn main() { diff --git a/src/sim/creature.rs b/src/sim/creature.rs index 4146a84..713223a 100644 --- a/src/sim/creature.rs +++ b/src/sim/creature.rs @@ -47,11 +47,7 @@ impl Creature { } pub fn patrol_chance(&self) -> f32 { - if self.definition.patrols { - 0.5 - } else { - 0.0 - } + if self.definition.patrols { 0.5 } else { 0.0 } } pub fn health(&self) -> i32 { diff --git a/src/sim/definitions/site.rs b/src/sim/definitions/site.rs index dcc4aa5..c1fb897 100644 --- a/src/sim/definitions/site.rs +++ b/src/sim/definitions/site.rs @@ -24,6 +24,7 @@ impl SiteDef { match tag.name.as_str() { "SIZE" => (result.min_size, result.max_size) = tag.parse_value().unwrap(), "NATURAL" => result.is_natural = true, + "UNDERGROUND" => result.is_underground = true, _ => { // warn!("Unknown tag '{}' in SITE defs", tag.name); return Err(ParseError::UnknownTag { diff --git a/src/sim/site.rs b/src/sim/site.rs index 949bff6..d6dfb12 100644 --- a/src/sim/site.rs +++ b/src/sim/site.rs @@ -24,10 +24,11 @@ impl Display for SiteId { #[derive(Debug, Serialize, Deserialize, Reflect)] pub struct Site { id: SiteId, - areas: Vec, accumulated_time: Duration, pub name: String, pub definition: SiteDef, + pub position: Coords, + pub areas: Vec, } impl Site { @@ -36,11 +37,17 @@ impl Site { Duration::from_secs(3600) } - pub fn new(definition: SiteDef, name: impl Into, areas: Vec) -> Self { + pub fn new( + definition: SiteDef, + name: impl Into, + position: Coords, + areas: Vec, + ) -> Self { Self { id: SiteId::generate(), - definition, name: name.into(), + definition, + position, areas, accumulated_time: Duration::default(), } @@ -139,10 +146,15 @@ impl Site { println!("Tick! {self}"); } - pub fn generate_from_def(site_def: &SiteDef) -> Self { + pub fn generate_from_def( + site_def: &SiteDef, + name: impl Into, + position: Coords, + ) -> Self { Self::new( site_def.clone(), - "Gorbo's cave", + name, + position, vec![ SiteArea::default(); fastrand::usize(site_def.min_size as usize..=site_def.max_size as usize) @@ -150,14 +162,6 @@ impl Site { ) } - pub fn areas(&self) -> &Vec { - self.areas.as_ref() - } - - pub fn areas_mut(&mut self) -> &mut Vec { - self.areas.as_mut() - } - pub fn populate_randomly(&mut self, creature_defs: &[&CreatureDef]) { let area_count = self.areas.len(); { diff --git a/src/sim/travel_group.rs b/src/sim/travel_group.rs index 650bee1..1e954dd 100644 --- a/src/sim/travel_group.rs +++ b/src/sim/travel_group.rs @@ -23,7 +23,7 @@ impl Display for TravelGroupId { #[derive(Clone, Copy, Eq, PartialEq, Debug, Serialize, Deserialize, Reflect)] pub enum WorldPoint { - Coords(WorldCoords), + Coords(Coords), Site(SiteId), } @@ -41,7 +41,7 @@ pub struct TravelGroup { id: TravelGroupId, accumulated_movement: Kilometer, pub creatures: Vec, - pub position: WorldCoords, + pub position: Coords, pub origin: Option, pub destination: Option, } @@ -49,7 +49,7 @@ pub struct TravelGroup { impl TravelGroup { pub fn new( creatures: Vec, - position: WorldCoords, + position: Coords, origin: Option, destination: Option, ) -> Self { diff --git a/src/sim/util.rs b/src/sim/util.rs index d70e3a7..bf6ea77 100644 --- a/src/sim/util.rs +++ b/src/sim/util.rs @@ -1,4 +1,4 @@ -use std::{fmt::Display, path::PathBuf}; +use std::{fmt::Display, ops::Range, path::PathBuf}; use bevy::prelude::Reflect; use serde::{Deserialize, Serialize}; @@ -6,12 +6,62 @@ use serde::{Deserialize, Serialize}; pub type Kilometer = f32; #[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Hash, Reflect)] -pub struct WorldCoords { +pub struct Coords { pub x: i32, pub y: i32, } -impl Display for WorldCoords { +impl Coords { + pub fn new(x: i32, y: i32) -> Self { + Self { x, y } + } +} + +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Hash, Reflect)] +pub struct CoordRect { + pub origin: Coords, + pub size: Coords, +} + +impl CoordRect { + pub fn from_size(center: Coords, size: Coords) -> Self { + Self { + origin: Coords { + x: center.x - size.x / 2, + y: center.y - size.y / 2, + }, + size, + } + } + + pub fn is_empty(&self) -> bool { + self.size.x <= 0 || self.size.y <= 0 + } + + pub fn width_range(&self) -> Range { + self.origin.x..(self.origin.x + self.size.x) + } + + pub fn height_range(&self) -> Range { + self.origin.y..(self.origin.y + self.size.y) + } + + /// Return iterator of all possible coordinates in rect, also known as the cartesian product of + /// the x and y ranges + pub fn iter_coords(&self) -> impl Iterator { + self.width_range() + .flat_map(|x| self.height_range().map(move |y| Coords::new(x, y))) + } + + pub fn pick_random_coords(&self, count: usize) -> Vec { + if self.is_empty() { + return vec![]; + } + fastrand::choose_multiple(self.iter_coords(), count) + } +} + +impl Display for Coords { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "({}, {})", self.x, self.y) } diff --git a/src/sim/world.rs b/src/sim/world.rs index 867b00b..295a8e2 100644 --- a/src/sim/world.rs +++ b/src/sim/world.rs @@ -5,33 +5,34 @@ use serde::{Deserialize, Serialize}; use super::prelude::*; -#[derive(Debug, Default, Resource, Serialize, Deserialize, Reflect)] +#[derive(Debug, Resource, Serialize, Deserialize, Reflect)] #[reflect(Resource)] pub struct GameWorld { + pub area: CoordRect, pub sites: Vec, pub travel_groups: Vec, } -impl GameWorld { - pub fn advance_time(&mut self, time: Duration) { - for site in self.sites.iter_mut() { - site.advance_time(time); - } - for travel_group in self.travel_groups.iter_mut() { - travel_group.advance_time(time); +impl Default for GameWorld { + fn default() -> Self { + Self { + area: CoordRect::from_size(Coords::new(0, 0), Coords::new(16, 16)), + sites: vec![], + travel_groups: vec![], } } +} +impl GameWorld { pub fn generate_mock(defs: &Definitions) -> Self { let cave_def = defs.sites.get("CAVE").unwrap(); - let mut site = Site::generate_from_def(cave_def); - site.populate_randomly(&defs.creatures.values().collect::>()); let bandit_def = defs.creatures.get("BANDIT").unwrap(); let spider_def = defs.creatures.get("SPIDER").unwrap(); let cave = Site::new( cave_def.clone(), "Gorbo's cave", + Coords::new(0, 0), vec![ SiteArea::from_creatures(&vec![ Creature::generate_from_def(defs.creatures.get("DRAGON").unwrap().clone()), @@ -61,13 +62,58 @@ impl GameWorld { Creature::generate_from_def(bandit_def.clone()), Creature::generate_from_def(bandit_def.clone()), ], - WorldCoords { x: 0, y: 0 }, - Some(WorldPoint::Coords(WorldCoords { x: 0, y: 0 })), + Coords { x: 0, y: 0 }, + Some(WorldPoint::Coords(Coords { x: 0, y: 0 })), Some(WorldPoint::Site(cave.id())), ); Self { sites: vec![cave], travel_groups: vec![group], + ..default() + } + } + + pub fn generate_random(defs: &Definitions) -> Self { + let mut world = Self { + area: CoordRect::from_size(Coords::new(0, 0), Coords::new(32, 32)), + ..default() + }; + + const SITE_COUNT: usize = 30; + let site_locations = world.area.pick_random_coords(SITE_COUNT); + let site_locations = site_locations.chunks(SITE_COUNT / 3); + + for (chunk, locations) in site_locations.enumerate() { + for (i, coords) in locations.iter().enumerate() { + world.sites.push(match chunk { + 0 => Site::generate_from_def( + defs.sites.get("DUNGEON").unwrap(), + format!("Dungeon #{}", i + 1), + *coords, + ), + 1 => Site::generate_from_def( + defs.sites.get("CAVE").unwrap(), + format!("Cave #{}", i + 1), + *coords, + ), + _ => Site::generate_from_def( + defs.sites.get("BANDIT_CAMP").unwrap(), + format!("Bandit camp #{}", i + 1), + *coords, + ), + }) + } + } + + world + } + + pub fn advance_time(&mut self, time: Duration) { + for site in self.sites.iter_mut() { + site.advance_time(time); + } + for travel_group in self.travel_groups.iter_mut() { + travel_group.advance_time(time); } } } diff --git a/src/sim_runner.rs b/src/sim_runner.rs deleted file mode 100644 index 0f0fef1..0000000 --- a/src/sim_runner.rs +++ /dev/null @@ -1,126 +0,0 @@ -use crate::sim; -use std::{collections::HashMap, io::BufReader, path::PathBuf, time::Duration}; - -use sim::definitions::parser::DefinitionParser; -use sim::prelude::*; - -const SAVE_FILE: &str = "world.bin"; - -pub fn run_interactive_simulation() { - let mut parse_error_files = vec![]; - let mut site_definitions = HashMap::new(); - let mut creature_definitions = HashMap::new(); - for entry in std::fs::read_dir(resources_path()).unwrap().flatten() { - if entry.file_name().to_string_lossy().ends_with(".def") { - let source = BufReader::new(std::fs::File::open(entry.path()).unwrap()); - match DefinitionParser::parse(source) { - Ok(defs) => { - for def in defs.0 { - if let Some(prev) = site_definitions.insert(def.id.clone(), def) { - eprintln!("Duplicate site definition '{}'", prev.id); - } - } - for def in defs.1 { - if let Some(prev) = creature_definitions.insert(def.id.clone(), def) { - eprintln!("Duplicate site definition '{}'", prev.id); - } - } - } - Err(err) => { - parse_error_files.push((err, entry.path())); - } - } - } - } - - for (err, path) in parse_error_files.iter() { - eprintln!("Error\t{path:?}\n\t{err:?}") - } - if !parse_error_files.is_empty() { - eprintln!("{} file(s) had parsing errors!", parse_error_files.len()) - } - - let site_def = site_definitions.get("CAVE").unwrap(); - let mut world = match std::fs::read(SAVE_FILE) { - Ok(data) => bincode::deserialize(&data).expect("Loading world data from file"), - Err(_) => { - let mut world = GameWorld::default(); - - let mut site = Site::generate_from_def(site_def); - site.populate_randomly(&creature_definitions.values().collect::>()); - - let bandit_def = creature_definitions.get("BANDIT").unwrap(); - let spider_def = creature_definitions.get("SPIDER").unwrap(); - let site = Site::new( - site_def.clone(), - "Gorbo's cave", - vec![ - SiteArea::from_creatures(&vec![ - Creature::generate_from_def( - creature_definitions.get("DRAGON").unwrap().clone(), - ), - Creature::generate_from_def( - creature_definitions.get("INDESTRUCTIBLE").unwrap().clone(), - ), - ]), - SiteArea::from_creatures(&vec![ - Creature::generate_from_def(bandit_def.clone()), - Creature::generate_from_def(spider_def.clone()), - ]), - SiteArea::from_creatures(&vec![ - Creature::generate_from_def(bandit_def.clone()), - Creature::generate_from_def(bandit_def.clone()), - Creature::generate_from_def(bandit_def.clone()), - ]), - SiteArea::from_creatures(&vec![ - Creature::generate_from_def(spider_def.clone()), - Creature::generate_from_def(spider_def.clone()), - ]), - ], - ); - - let group = TravelGroup::new( - vec![ - Creature::generate_from_def(bandit_def.clone()), - Creature::generate_from_def(bandit_def.clone()), - Creature::generate_from_def(bandit_def.clone()), - ], - WorldCoords { x: 0, y: 0 }, - Some(WorldPoint::Coords(WorldCoords { x: 0, y: 0 })), - Some(WorldPoint::Site(site.id())), - ); - - world.sites.push(site); - world.travel_groups.push(group); - std::fs::write("world.bin", bincode::serialize(&world).unwrap()).unwrap(); - world - } - }; - - for site in world.sites.iter() { - println!("{site}"); - } - - for group in world.travel_groups.iter() { - println!("{group}"); - } - - let mut input = String::new(); - loop { - std::io::stdin().read_line(&mut input).unwrap(); - if ["q", "quit", "exit"] - .iter() - .any(|quit_cmd| input.trim() == *quit_cmd) - { - break; - } - let start = std::time::Instant::now(); - world.advance_time(Duration::from_secs(3600)); - let end = std::time::Instant::now(); - println!("World tick: {}us", (end - start).as_micros()); - } -} - -fn resources_path() -> PathBuf { - PathBuf::from(".").join("defs") -}