From 94bef521cea171bb8b11543cceb82f44b9e84669 Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Sat, 26 Mar 2022 22:52:29 +0100 Subject: [PATCH 01/15] new test to check different resource situations --- world/src/sim2/mod.rs | 193 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 185 insertions(+), 8 deletions(-) diff --git a/world/src/sim2/mod.rs b/world/src/sim2/mod.rs index 709050a330..3e5473e67b 100644 --- a/world/src/sim2/mod.rs +++ b/world/src/sim2/mod.rs @@ -282,24 +282,22 @@ pub fn tick(index: &mut Index, _world: &mut WorldSim, dt: f32, _vc: vergleich::C #[cfg(test)] mod tests { use crate::{sim, site::economy::GoodMap, util::seed_expan}; - use common::trade::Good; + use common::{store::Id, terrain::BiomeKind, trade::Good}; use rand::SeedableRng; use rand_chacha::ChaChaRng; use serde::{Deserialize, Serialize}; use std::convert::TryInto; use tracing::{info, Level}; - use tracing_subscriber::{ - filter::{EnvFilter, LevelFilter}, - FmtSubscriber, - }; + use tracing_subscriber::{filter::EnvFilter, FmtSubscriber}; use vek::Vec2; // enable info! fn init() { FmtSubscriber::builder() - .with_max_level(Level::ERROR) - .with_env_filter(EnvFilter::from_default_env().add_directive(LevelFilter::INFO.into())) - .init(); + .with_max_level(Level::INFO) + .with_env_filter(EnvFilter::from_default_env()) + .try_init() + .unwrap_or_default(); } #[derive(Debug, Serialize, Deserialize)] @@ -444,4 +442,183 @@ mod tests { } crate::sim2::simulate(&mut index, &mut sim); } + + struct Simenv { + index: crate::index::Index, + rng: ChaChaRng, + targets: hashbrown::HashMap, f32>, + } + + #[test] + // test whether a site in moderate climate can survive on its own + fn test_economy_moderate_standalone() { + fn add_settlement( + env: &mut Simenv, + // index: &mut crate::index::Index, + // rng: &mut ChaCha20Rng, + _name: &str, + target: f32, + resources: &[(Good, f32)], + ) -> Id { + let wpos = Vec2 { x: 42, y: 42 }; + let mut settlement = crate::site::Site::settlement(crate::site::Settlement::generate( + wpos, + None, + &mut env.rng, + //Some(name), + )); + for (good, amount) in resources.iter() { + settlement.economy.natural_resources.chunks_per_resource + [(*good).try_into().unwrap_or_default()] = *amount; + settlement.economy.natural_resources.average_yield_per_chunk + [(*good).try_into().unwrap_or_default()] = 1.0; + } + let id = env.index.sites.insert(settlement); + env.targets.insert(id, target); + id + } + + fn print_sorted( + prefix: &str, + mut list: Vec<(String, f32)>, + threshold: f32, + decimals: usize, + ) { + print!("{}", prefix); + list.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Less)); + for i in list.iter() { + if i.1 >= threshold { + print!("{}={:.*} ", i.0, decimals, i.1); + } + } + println!(); + } + + init(); + let threadpool = rayon::ThreadPoolBuilder::new().build().unwrap(); + info!("init"); + let seed = 59686; + let opts = sim::WorldOpts { + seed_elements: true, + world_file: sim::FileOpts::LoadAsset(sim::DEFAULT_WORLD_MAP.into()), + calendar: Default::default(), + }; + let index = crate::index::Index::new(seed); + info!("Index created"); + let mut sim = sim::WorldSim::generate(seed, opts, &threadpool); + info!("World loaded"); + let rng = ChaChaRng::from_seed(seed_expan::rng_state(seed)); + let mut env = Simenv { + index, + rng, + targets: hashbrown::HashMap::new(), + }; + add_settlement(&mut env, "Grass", 18.0, &[( + Good::Terrain(BiomeKind::Grassland), + 100.0_f32, + )]); + add_settlement(&mut env, "Forest", 22.0, &[( + Good::Terrain(BiomeKind::Forest), + 100.0_f32, + )]); + add_settlement(&mut env, "Mountain", 19.0, &[( + Good::Terrain(BiomeKind::Mountain), + 100.0_f32, + )]); + add_settlement(&mut env, "Desert", 19.0, &[( + Good::Terrain(BiomeKind::Desert), + 100.0_f32, + )]); + // add_settlement(&mut index, &mut rng, &[ + // (Good::Terrain(BiomeKind::Jungle), 100.0_f32), + // ]); + // add_settlement(&mut index, &mut rng, &[ + // (Good::Terrain(BiomeKind::Snowland), 100.0_f32), + // ]); + add_settlement(&mut env, "GrFoMo", 30.0, &[ + (Good::Terrain(BiomeKind::Grassland), 100.0_f32), + (Good::Terrain(BiomeKind::Forest), 100.0_f32), + (Good::Terrain(BiomeKind::Mountain), 10.0_f32), + ]); + add_settlement(&mut env, "MountainCave", 19.0, &[ + (Good::Terrain(BiomeKind::Mountain), 100.0_f32), + // (Good::CaveAccess, 100.0_f32), + ]); + crate::sim2::simulate(&mut env.index, &mut sim); + use crate::site::economy::good_list; + for (id, site) in env.index.sites.iter() { + println!("Site id {:?} name {}", id, site.name()); + //assert!(site.economy.sanity_check()); + print!(" Resources: "); + for i in good_list() { + let amount = site.economy.natural_resources.chunks_per_resource[i]; + if amount > 0.0 { + print!("{:?}={} ", i, amount); + } + } + println!(); + println!(" Population {:.1}, limited by ?", site.economy.pop); + let idle: f32 = + site.economy.pop * (1.0 - site.economy.labors.iter().map(|(_, a)| *a).sum::()); + print_sorted( + &format!(" Professions: idle={:.1} ", idle), + site.economy + .labors + .iter() + .map(|(l, a)| (format!("{:?}", l), *a * site.economy.pop)) + .collect(), + site.economy.pop * 0.05, + 1, + ); + print_sorted( + " Stock: ", + site.economy + .stocks + .iter() + .map(|(l, a)| (format!("{:?}", l), *a)) + .collect(), + 1.0, + 0, + ); + print_sorted( + " Values: ", + site.economy + .values + .iter() + .map(|(l, a)| { + ( + format!("{:?}", l), + a.map(|v| if v > 3.9 { 0.0 } else { v }).unwrap_or(0.0), + ) + }) + .collect(), + 0.1, + 1, + ); + print_sorted( + " Labor Values: ", + site.economy + .labor_values + .iter() + .map(|(l, a)| (format!("{:?}", l), a.unwrap_or(0.0))) + .collect(), + 0.1, + 1, + ); + // print!(" Limited: "); + // for (limit, prod) in site + // .economy + // .limited_by + // .iter() + // .zip(site.economy.productivity.iter()) + // { + // if 0.01 <= *prod.1 && *prod.1 <= 0.99 { + // print!("{:?}:{:?}={} ", limit.0, limit.1, *prod.1); + // } + // } + // println!(); + // check population (shrinks if economy gets broken) + // assert!(site.economy.pop >= env.targets[&id]); + } + } } From faf12118f0ae2c4c88ac8cefa068261ae6f24d0c Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Sun, 27 Mar 2022 22:16:05 +0200 Subject: [PATCH 02/15] remember which good a profession is missing for productivity --- world/src/sim2/mod.rs | 29 ++++++++++++++++------------- world/src/site/economy/mod.rs | 26 +++++++++++++++++++++----- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/world/src/sim2/mod.rs b/world/src/sim2/mod.rs index 3e5473e67b..5eb38f4c22 100644 --- a/world/src/sim2/mod.rs +++ b/world/src/sim2/mod.rs @@ -557,7 +557,10 @@ mod tests { } } println!(); - println!(" Population {:.1}, limited by ?", site.economy.pop); + println!( + " Population {:.1}, limited by {:?}", + site.economy.pop, site.economy.population_limited_by + ); let idle: f32 = site.economy.pop * (1.0 - site.economy.labors.iter().map(|(_, a)| *a).sum::()); print_sorted( @@ -605,18 +608,18 @@ mod tests { 0.1, 1, ); - // print!(" Limited: "); - // for (limit, prod) in site - // .economy - // .limited_by - // .iter() - // .zip(site.economy.productivity.iter()) - // { - // if 0.01 <= *prod.1 && *prod.1 <= 0.99 { - // print!("{:?}:{:?}={} ", limit.0, limit.1, *prod.1); - // } - // } - // println!(); + print!(" Limited: "); + for (limit, prod) in site + .economy + .limited_by + .iter() + .zip(site.economy.productivity.iter()) + { + if 0.01 <= *prod.1 && *prod.1 <= 0.99 { + print!("{:?}:{:?}={} ", limit.0, limit.1, *prod.1); + } + } + println!(); // check population (shrinks if economy gets broken) // assert!(site.economy.pop >= env.targets[&id]); } diff --git a/world/src/site/economy/mod.rs b/world/src/site/economy/mod.rs index 99669047a9..778eb1043f 100644 --- a/world/src/site/economy/mod.rs +++ b/world/src/site/economy/mod.rs @@ -88,6 +88,7 @@ lazy_static! { pub struct Economy { /// Population pub pop: f32, + pub population_limited_by: GoodIndex, /// Total available amount of each good pub stocks: GoodMap, @@ -110,9 +111,9 @@ pub struct Economy { // Per worker, per year, of their output good pub yields: LaborMap, pub productivity: LaborMap, + pub limited_by: LaborMap, pub natural_resources: NaturalResources, - // usize is distance pub neighbors: Vec, /// outgoing trade, per provider @@ -126,6 +127,7 @@ impl Default for Economy { let coin_index: GoodIndex = GoodIndex::try_from(Coin).unwrap_or_default(); Self { pop: 32.0, + population_limited_by: GoodIndex::default(), stocks: GoodMap::from_list(&[(coin_index, Economy::STARTING_COIN)], 100.0), surplus: Default::default(), @@ -140,6 +142,7 @@ impl Default for Economy { labors: LaborMap::from_default(0.01), yields: LaborMap::from_default(1.0), productivity: LaborMap::from_default(1.0), + limited_by: LaborMap::from_default(GoodIndex::default()), natural_resources: Default::default(), neighbors: Default::default(), @@ -928,7 +931,7 @@ impl Economy { // available then we only need to consume 2/3rds // of other ingredients and leave the rest in stock // In effect, this is the productivity - let labor_productivity = orders + let (labor_productivity, limited_by) = orders .iter() .map(|(good, amount)| { // What quantity is this order requesting? @@ -936,13 +939,20 @@ impl Economy { assert!(stocks_before[*good] >= 0.0); assert!(demand[*good] >= 0.0); // What proportion of this order is the economy able to satisfy? - (stocks_before[*good] / demand[*good]).min(1.0) + ((stocks_before[*good] / demand[*good]).min(1.0), *good) }) - .min_by(|a, b| a.partial_cmp(b).unwrap_or(Less)) + .min_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Less)) .unwrap_or_else(|| { panic!("Industry {:?} requires at least one input order", labor) }); assert!(labor_productivity >= 0.0); + if let Some(labor) = labor { + self.limited_by[*labor] = if labor_productivity >= 1.0 { + GoodIndex::default() + } else { + limited_by + }; + } let mut total_materials_cost = 0.0; for (good, amount) in orders { @@ -1069,7 +1079,8 @@ impl Economy { // Births/deaths const NATURAL_BIRTH_RATE: f32 = 0.05; const DEATH_RATE: f32 = 0.005; - let birth_rate = if self.surplus[*FOOD_INDEX] > 0.0 { + let population_growth = self.surplus[*FOOD_INDEX] > 0.0; + let birth_rate = if population_growth { NATURAL_BIRTH_RATE } else { 0.0 @@ -1078,6 +1089,11 @@ impl Economy { "pop", dt / DAYS_PER_YEAR * self.pop * (birth_rate - DEATH_RATE), ); + self.population_limited_by = if population_growth { + GoodIndex::default() + } else { + *FOOD_INDEX + }; // calculate the new unclaimed stock //let next_orders = self.get_orders(); From 6f431a6fff504305f5e7f50c415dd591896c3d40 Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Sun, 27 Mar 2022 23:43:32 +0200 Subject: [PATCH 03/15] rearrange and connect towns (shows planning weakness) --- world/src/sim2/mod.rs | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/world/src/sim2/mod.rs b/world/src/sim2/mod.rs index 5eb38f4c22..31d0c2e68a 100644 --- a/world/src/sim2/mod.rs +++ b/world/src/sim2/mod.rs @@ -513,22 +513,22 @@ mod tests { rng, targets: hashbrown::HashMap::new(), }; - add_settlement(&mut env, "Grass", 18.0, &[( - Good::Terrain(BiomeKind::Grassland), - 100.0_f32, - )]); add_settlement(&mut env, "Forest", 22.0, &[( Good::Terrain(BiomeKind::Forest), 100.0_f32, )]); - add_settlement(&mut env, "Mountain", 19.0, &[( - Good::Terrain(BiomeKind::Mountain), - 100.0_f32, - )]); - add_settlement(&mut env, "Desert", 19.0, &[( - Good::Terrain(BiomeKind::Desert), + add_settlement(&mut env, "Grass", 18.0, &[( + Good::Terrain(BiomeKind::Grassland), 100.0_f32, )]); + // add_settlement(&mut env, "Mountain", 19.0, &[( + // Good::Terrain(BiomeKind::Mountain), + // 100.0_f32, + // )]); + // add_settlement(&mut env, "Desert", 19.0, &[( + // Good::Terrain(BiomeKind::Desert), + // 100.0_f32, + // )]); // add_settlement(&mut index, &mut rng, &[ // (Good::Terrain(BiomeKind::Jungle), 100.0_f32), // ]); @@ -540,10 +540,25 @@ mod tests { (Good::Terrain(BiomeKind::Forest), 100.0_f32), (Good::Terrain(BiomeKind::Mountain), 10.0_f32), ]); - add_settlement(&mut env, "MountainCave", 19.0, &[ + add_settlement(&mut env, "Mountain", 19.0, &[ (Good::Terrain(BiomeKind::Mountain), 100.0_f32), // (Good::CaveAccess, 100.0_f32), ]); + // connect to neighbors + for i in 1..(env.index.sites.ids().count() as u64 - 1) { + let previous = env.index.sites.recreate_id(i - 1); + let next = env.index.sites.recreate_id(i + 1); + let center = env.index.sites.recreate_id(i); + center + .zip(previous) + .zip(next) + .map(|((center, previous), next)| { + env.index.sites[center].economy.add_neighbor(next, 1); + env.index.sites[next].economy.add_neighbor(center, 1); + env.index.sites[center].economy.add_neighbor(previous, 2); + env.index.sites[previous].economy.add_neighbor(center, 2); + }); + } crate::sim2::simulate(&mut env.index, &mut sim); use crate::site::economy::good_list; for (id, site) in env.index.sites.iter() { From 57dafea9b40dfd209b2b94baa948220e6d17810c Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Sun, 27 Mar 2022 23:54:09 +0200 Subject: [PATCH 04/15] group the first three settlements together (should enable them to survive by trade) --- world/src/sim2/mod.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/world/src/sim2/mod.rs b/world/src/sim2/mod.rs index 31d0c2e68a..6a49358d7c 100644 --- a/world/src/sim2/mod.rs +++ b/world/src/sim2/mod.rs @@ -521,10 +521,10 @@ mod tests { Good::Terrain(BiomeKind::Grassland), 100.0_f32, )]); - // add_settlement(&mut env, "Mountain", 19.0, &[( - // Good::Terrain(BiomeKind::Mountain), - // 100.0_f32, - // )]); + add_settlement(&mut env, "Mountain", 19.0, &[( + Good::Terrain(BiomeKind::Mountain), + 100.0_f32, + )]); // add_settlement(&mut env, "Desert", 19.0, &[( // Good::Terrain(BiomeKind::Desert), // 100.0_f32, @@ -540,10 +540,10 @@ mod tests { (Good::Terrain(BiomeKind::Forest), 100.0_f32), (Good::Terrain(BiomeKind::Mountain), 10.0_f32), ]); - add_settlement(&mut env, "Mountain", 19.0, &[ - (Good::Terrain(BiomeKind::Mountain), 100.0_f32), - // (Good::CaveAccess, 100.0_f32), - ]); + // add_settlement(&mut env, "Mountain", 19.0, &[ + // (Good::Terrain(BiomeKind::Mountain), 100.0_f32), + // // (Good::CaveAccess, 100.0_f32), + // ]); // connect to neighbors for i in 1..(env.index.sites.ids().count() as u64 - 1) { let previous = env.index.sites.recreate_id(i - 1); From b144ed42e3b64b63d05d7479f85251a2d52ec88f Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Wed, 30 Mar 2022 23:38:08 +0200 Subject: [PATCH 05/15] display trade volume --- world/src/sim2/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/world/src/sim2/mod.rs b/world/src/sim2/mod.rs index 6a49358d7c..fd31b97616 100644 --- a/world/src/sim2/mod.rs +++ b/world/src/sim2/mod.rs @@ -635,6 +635,13 @@ mod tests { } } println!(); + print!(" Trade: "); + for (g, &amt) in site.economy.active_exports.iter() { + if amt < -0.1 || amt > 0.1 { + print!("{:?}={:.2} ", g, amt); + } + } + println!(); // check population (shrinks if economy gets broken) // assert!(site.economy.pop >= env.targets[&id]); } From 8e48d595390a9cfe70f502f973983991fa3a94a4 Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Wed, 30 Mar 2022 23:48:52 +0200 Subject: [PATCH 06/15] also print situation in other econ test --- world/src/sim2/mod.rs | 207 +++++++++++++++++++++--------------------- 1 file changed, 106 insertions(+), 101 deletions(-) diff --git a/world/src/sim2/mod.rs b/world/src/sim2/mod.rs index fd31b97616..9ae7d1028c 100644 --- a/world/src/sim2/mod.rs +++ b/world/src/sim2/mod.rs @@ -315,6 +315,110 @@ mod tests { resources: Vec, } + fn print_sorted( + prefix: &str, + mut list: Vec<(String, f32)>, + threshold: f32, + decimals: usize, + ) { + print!("{}", prefix); + list.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Less)); + for i in list.iter() { + if i.1 >= threshold { + print!("{}={:.*} ", i.0, decimals, i.1); + } + } + println!(); + } + + fn show_economy(sites: &common::store::Store) { + use crate::site::economy::good_list; + for (id, site) in sites.iter() { + println!("Site id {:?} name {}", id, site.name()); + //assert!(site.economy.sanity_check()); + print!(" Resources: "); + for i in good_list() { + let amount = site.economy.natural_resources.chunks_per_resource[i]; + if amount > 0.0 { + print!("{:?}={} ", i, amount); + } + } + println!(); + println!( + " Population {:.1}, limited by {:?}", + site.economy.pop, site.economy.population_limited_by + ); + let idle: f32 = + site.economy.pop * (1.0 - site.economy.labors.iter().map(|(_, a)| *a).sum::()); + print_sorted( + &format!(" Professions: idle={:.1} ", idle), + site.economy + .labors + .iter() + .map(|(l, a)| (format!("{:?}", l), *a * site.economy.pop)) + .collect(), + site.economy.pop * 0.05, + 1, + ); + print_sorted( + " Stock: ", + site.economy + .stocks + .iter() + .map(|(l, a)| (format!("{:?}", l), *a)) + .collect(), + 1.0, + 0, + ); + print_sorted( + " Values: ", + site.economy + .values + .iter() + .map(|(l, a)| { + ( + format!("{:?}", l), + a.map(|v| if v > 3.9 { 0.0 } else { v }).unwrap_or(0.0), + ) + }) + .collect(), + 0.1, + 1, + ); + print_sorted( + " Labor Values: ", + site.economy + .labor_values + .iter() + .map(|(l, a)| (format!("{:?}", l), a.unwrap_or(0.0))) + .collect(), + 0.1, + 1, + ); + print!(" Limited: "); + for (limit, prod) in site + .economy + .limited_by + .iter() + .zip(site.economy.productivity.iter()) + { + if 0.01 <= *prod.1 && *prod.1 <= 0.99 { + print!("{:?}:{:?}={} ", limit.0, limit.1, *prod.1); + } + } + println!(); + print!(" Trade: "); + for (g, &amt) in site.economy.active_exports.iter() { + if amt < -0.1 || amt > 0.1 { + print!("{:?}={:.2} ", g, amt); + } + } + println!(); + // check population (shrinks if economy gets broken) + // assert!(site.economy.pop >= env.targets[&id]); + } + } + #[test] fn test_economy() { init(); @@ -441,6 +545,7 @@ mod tests { } } crate::sim2::simulate(&mut index, &mut sim); + show_economy(&index.sites); } struct Simenv { @@ -478,22 +583,6 @@ mod tests { id } - fn print_sorted( - prefix: &str, - mut list: Vec<(String, f32)>, - threshold: f32, - decimals: usize, - ) { - print!("{}", prefix); - list.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Less)); - for i in list.iter() { - if i.1 >= threshold { - print!("{}={:.*} ", i.0, decimals, i.1); - } - } - println!(); - } - init(); let threadpool = rayon::ThreadPoolBuilder::new().build().unwrap(); info!("init"); @@ -560,90 +649,6 @@ mod tests { }); } crate::sim2::simulate(&mut env.index, &mut sim); - use crate::site::economy::good_list; - for (id, site) in env.index.sites.iter() { - println!("Site id {:?} name {}", id, site.name()); - //assert!(site.economy.sanity_check()); - print!(" Resources: "); - for i in good_list() { - let amount = site.economy.natural_resources.chunks_per_resource[i]; - if amount > 0.0 { - print!("{:?}={} ", i, amount); - } - } - println!(); - println!( - " Population {:.1}, limited by {:?}", - site.economy.pop, site.economy.population_limited_by - ); - let idle: f32 = - site.economy.pop * (1.0 - site.economy.labors.iter().map(|(_, a)| *a).sum::()); - print_sorted( - &format!(" Professions: idle={:.1} ", idle), - site.economy - .labors - .iter() - .map(|(l, a)| (format!("{:?}", l), *a * site.economy.pop)) - .collect(), - site.economy.pop * 0.05, - 1, - ); - print_sorted( - " Stock: ", - site.economy - .stocks - .iter() - .map(|(l, a)| (format!("{:?}", l), *a)) - .collect(), - 1.0, - 0, - ); - print_sorted( - " Values: ", - site.economy - .values - .iter() - .map(|(l, a)| { - ( - format!("{:?}", l), - a.map(|v| if v > 3.9 { 0.0 } else { v }).unwrap_or(0.0), - ) - }) - .collect(), - 0.1, - 1, - ); - print_sorted( - " Labor Values: ", - site.economy - .labor_values - .iter() - .map(|(l, a)| (format!("{:?}", l), a.unwrap_or(0.0))) - .collect(), - 0.1, - 1, - ); - print!(" Limited: "); - for (limit, prod) in site - .economy - .limited_by - .iter() - .zip(site.economy.productivity.iter()) - { - if 0.01 <= *prod.1 && *prod.1 <= 0.99 { - print!("{:?}:{:?}={} ", limit.0, limit.1, *prod.1); - } - } - println!(); - print!(" Trade: "); - for (g, &amt) in site.economy.active_exports.iter() { - if amt < -0.1 || amt > 0.1 { - print!("{:?}={:.2} ", g, amt); - } - } - println!(); - // check population (shrinks if economy gets broken) - // assert!(site.economy.pop >= env.targets[&id]); - } + show_economy(&env.index.sites); } } From eedfce45bc2fb84f3e36e33db3e55b7131a12e17 Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Wed, 30 Mar 2022 23:59:46 +0200 Subject: [PATCH 07/15] also output normal economic simulation --- world/src/sim2/mod.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/world/src/sim2/mod.rs b/world/src/sim2/mod.rs index 9ae7d1028c..77f55d45b1 100644 --- a/world/src/sim2/mod.rs +++ b/world/src/sim2/mod.rs @@ -420,7 +420,29 @@ mod tests { } #[test] - fn test_economy() { + fn test_economy0() { + init(); + let threadpool = rayon::ThreadPoolBuilder::new().build().unwrap(); + info!("init"); + let seed = 59686; + let opts = sim::WorldOpts { + seed_elements: true, + world_file: sim::FileOpts::LoadAsset(sim::DEFAULT_WORLD_MAP.into()), + //sim::FileOpts::LoadAsset("world.map.economy_8x8".into()), + calendar: None, + }; + let mut index = crate::index::Index::new(seed); + info!("Index created"); + let mut sim = sim::WorldSim::generate(seed, opts, &threadpool); + info!("World loaded"); + let _civs = crate::civ::Civs::generate(seed, &mut sim, &mut index); + info!("Civs created"); + crate::sim2::simulate(&mut index, &mut sim); + show_economy(&index.sites); + } + + #[test] + fn test_economy1() { init(); let threadpool = rayon::ThreadPoolBuilder::new().build().unwrap(); info!("init"); From d897df4b78e5a3b07e78206f00e9e6963e75c16f Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Thu, 31 Mar 2022 00:08:28 +0200 Subject: [PATCH 08/15] output number of trade partners --- world/src/sim2/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world/src/sim2/mod.rs b/world/src/sim2/mod.rs index 77f55d45b1..22680ab86a 100644 --- a/world/src/sim2/mod.rs +++ b/world/src/sim2/mod.rs @@ -407,7 +407,7 @@ mod tests { } } println!(); - print!(" Trade: "); + print!(" Trade({}): ", site.economy.neighbors.len()); for (g, &amt) in site.economy.active_exports.iter() { if amt < -0.1 || amt > 0.1 { print!("{:?}={:.2} ", g, amt); From dc4b9dc19e876b1d4c5b13aa334c7637c91b66b5 Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Fri, 1 Apr 2022 22:29:32 +0200 Subject: [PATCH 09/15] more readable and meaningful csv output --- world/src/sim2/mod.rs | 55 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/world/src/sim2/mod.rs b/world/src/sim2/mod.rs index 22680ab86a..9fc80ea032 100644 --- a/world/src/sim2/mod.rs +++ b/world/src/sim2/mod.rs @@ -58,34 +58,59 @@ impl EconStatistics { pub fn csv_entry(f: &mut std::fs::File, site: &Site) -> Result<(), std::io::Error> { use std::io::Write; + use crate::site::economy::GoodIndex; write!( *f, - "{}, {}, {}, {},", + "{}, {}, {}, {:.1}, {},,", site.name(), site.get_origin().x, site.get_origin().y, - site.economy.pop + site.economy.pop, + site.economy.neighbors.len(), )?; for g in good_list() { - write!(*f, "{:?},", site.economy.values[g].unwrap_or(-1.0))?; + if let Some(value) = site.economy.values[g] { + write!(*f, "{:.2},", value)?; + } else { + f.write_all(b",")?; + } } + f.write_all(b",")?; for g in good_list() { - write!(f, "{:?},", site.economy.labor_values[g].unwrap_or(-1.0))?; + if let Some(labor_value) = site.economy.labor_values[g] { + write!(f, "{:.2},", labor_value)?; + } else { + f.write_all(b",")?; + } } + f.write_all(b",")?; for g in good_list() { - write!(f, "{:?},", site.economy.stocks[g])?; + write!(f, "{:.1},", site.economy.stocks[g])?; } + f.write_all(b",")?; for g in good_list() { - write!(f, "{:?},", site.economy.marginal_surplus[g])?; + write!(f, "{:.1},", site.economy.marginal_surplus[g])?; } + f.write_all(b",")?; for l in LaborIndex::list() { - write!(f, "{:?},", site.economy.labors[l] * site.economy.pop)?; + write!(f, "{:.1},", site.economy.labors[l] * site.economy.pop)?; } + f.write_all(b",")?; for l in LaborIndex::list() { - write!(f, "{:?},", site.economy.productivity[l])?; + write!(f, "{:.2},", site.economy.productivity[l])?; } + f.write_all(b",")?; for l in LaborIndex::list() { - write!(f, "{:?},", site.economy.yields[l])?; + write!(f, "{:.1},", site.economy.yields[l])?; + } + f.write_all(b",")?; + for l in LaborIndex::list() { + let limit = site.economy.limited_by[l]; + if limit == GoodIndex::default() { + f.write_all(b",")?; + } else { + write!(f, "{:?},", limit)?; + } } writeln!(f) } @@ -96,28 +121,38 @@ fn simulate_return(index: &mut Index, world: &mut WorldSim) -> Result<(), std::i // here let mut f = if GENERATE_CSV { let mut f = std::fs::File::create("economy.csv")?; - write!(f, "Site,PosX,PosY,Population,")?; + write!(f, "Site,PosX,PosY,Population,Neighbors,,")?; for g in good_list() { write!(f, "{:?} Value,", g)?; } + f.write_all(b",")?; for g in good_list() { write!(f, "{:?} LaborVal,", g)?; } + f.write_all(b",")?; for g in good_list() { write!(f, "{:?} Stock,", g)?; } + f.write_all(b",")?; for g in good_list() { write!(f, "{:?} Surplus,", g)?; } + f.write_all(b",")?; for l in LaborIndex::list() { write!(f, "{:?} Labor,", l)?; } + f.write_all(b",")?; for l in LaborIndex::list() { write!(f, "{:?} Productivity,", l)?; } + f.write_all(b",")?; for l in LaborIndex::list() { write!(f, "{:?} Yields,", l)?; } + f.write_all(b",")?; + for l in LaborIndex::list() { + write!(f, "{:?} limit,", l)?; + } writeln!(f)?; Some(f) } else { From 53ace17327bff63c6bb07ac0028e5ceff2aab3cc Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Fri, 1 Apr 2022 23:15:06 +0200 Subject: [PATCH 10/15] output trade to csv file --- world/src/sim2/mod.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/world/src/sim2/mod.rs b/world/src/sim2/mod.rs index 9fc80ea032..e5dfae1c1f 100644 --- a/world/src/sim2/mod.rs +++ b/world/src/sim2/mod.rs @@ -112,6 +112,14 @@ pub fn csv_entry(f: &mut std::fs::File, site: &Site) -> Result<(), std::io::Erro write!(f, "{:?},", limit)?; } } + f.write_all(b",")?; + for g in good_list() { + if site.economy.last_exports[g]>=0.1 || site.economy.last_exports[g]<=-0.1 { + write!(f, "{:.1},", site.economy.last_exports[g])?; + } else { + f.write_all(b",")?; + } + } writeln!(f) } @@ -153,6 +161,10 @@ fn simulate_return(index: &mut Index, world: &mut WorldSim) -> Result<(), std::i for l in LaborIndex::list() { write!(f, "{:?} limit,", l)?; } + f.write_all(b",")?; + for g in good_list() { + write!(f, "{:?} trade,", g)?; + } writeln!(f)?; Some(f) } else { From 8dfd6dc523d1d17b9fe69608332f97e2dc5da0b9 Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Sat, 2 Apr 2022 11:01:52 +0200 Subject: [PATCH 11/15] document more elements of economy --- world/src/site/economy/mod.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/world/src/site/economy/mod.rs b/world/src/site/economy/mod.rs index 778eb1043f..fdb0f29a8b 100644 --- a/world/src/site/economy/mod.rs +++ b/world/src/site/economy/mod.rs @@ -98,22 +98,29 @@ pub struct Economy { pub marginal_surplus: GoodMap, /// amount of wares not needed by the economy (helps with trade planning) pub unconsumed_stock: GoodMap, + /// Local availability of a good, 4.0 = starved, 2.0 = balanced, 0.1 = extra, NULL = way too much // For some goods, such a goods without any supply, it doesn't make sense to talk about value pub values: GoodMap>, + /// amount of goods exported/imported during the last cycle pub last_exports: GoodMap, pub active_exports: GoodMap, // unfinished trade (amount unconfirmed) //pub export_targets: GoodMap, + /// amount of labor that went into a good, [1 man cycle=1.0] pub labor_values: GoodMap>, + // this assumes a single source, replace with LaborMap? pub material_costs: GoodMap, - // Proportion of individuals dedicated to an industry + /// Proportion of individuals dedicated to an industry (sums to roughly 1.0) pub labors: LaborMap, // Per worker, per year, of their output good pub yields: LaborMap, + /// [0.0..1.0] pub productivity: LaborMap, + /// Missing raw material which limits production pub limited_by: LaborMap, pub natural_resources: NaturalResources, + /// Neighboring sites to trade with pub neighbors: Vec, /// outgoing trade, per provider From 2f7c50281fe583cad64d47a83190c45638334e84 Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Sat, 2 Apr 2022 12:11:40 +0200 Subject: [PATCH 12/15] fix neighbor logic in test, fix clippy, fix rustfmt --- world/src/sim2/mod.rs | 47 ++++++++++++++++------------------- world/src/site/economy/mod.rs | 3 ++- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/world/src/sim2/mod.rs b/world/src/sim2/mod.rs index e5dfae1c1f..a12675ac58 100644 --- a/world/src/sim2/mod.rs +++ b/world/src/sim2/mod.rs @@ -57,8 +57,8 @@ impl EconStatistics { } pub fn csv_entry(f: &mut std::fs::File, site: &Site) -> Result<(), std::io::Error> { - use std::io::Write; use crate::site::economy::GoodIndex; + use std::io::Write; write!( *f, "{}, {}, {}, {:.1}, {},,", @@ -114,7 +114,7 @@ pub fn csv_entry(f: &mut std::fs::File, site: &Site) -> Result<(), std::io::Erro } f.write_all(b",")?; for g in good_list() { - if site.economy.last_exports[g]>=0.1 || site.economy.last_exports[g]<=-0.1 { + if site.economy.last_exports[g] >= 0.1 || site.economy.last_exports[g] <= -0.1 { write!(f, "{:.1},", site.economy.last_exports[g])?; } else { f.write_all(b",")?; @@ -362,12 +362,7 @@ mod tests { resources: Vec, } - fn print_sorted( - prefix: &str, - mut list: Vec<(String, f32)>, - threshold: f32, - decimals: usize, - ) { + fn print_sorted(prefix: &str, mut list: Vec<(String, f32)>, threshold: f32, decimals: usize) { print!("{}", prefix); list.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Less)); for i in list.iter() { @@ -449,14 +444,14 @@ mod tests { .iter() .zip(site.economy.productivity.iter()) { - if 0.01 <= *prod.1 && *prod.1 <= 0.99 { + if (0.01..=0.99).contains(prod.1) { print!("{:?}:{:?}={} ", limit.0, limit.1, *prod.1); } } println!(); print!(" Trade({}): ", site.economy.neighbors.len()); for (g, &amt) in site.economy.active_exports.iter() { - if amt < -0.1 || amt > 0.1 { + if !(-0.1..=0.1).contains(&amt) { print!("{:?}={:.2} ", g, amt); } } @@ -671,15 +666,15 @@ mod tests { rng, targets: hashbrown::HashMap::new(), }; - add_settlement(&mut env, "Forest", 22.0, &[( + add_settlement(&mut env, "Forest", 5000.0, &[( Good::Terrain(BiomeKind::Forest), 100.0_f32, )]); - add_settlement(&mut env, "Grass", 18.0, &[( + add_settlement(&mut env, "Grass", 900.0, &[( Good::Terrain(BiomeKind::Grassland), 100.0_f32, )]); - add_settlement(&mut env, "Mountain", 19.0, &[( + add_settlement(&mut env, "Mountain", 3.0, &[( Good::Terrain(BiomeKind::Mountain), 100.0_f32, )]); @@ -693,7 +688,7 @@ mod tests { // add_settlement(&mut index, &mut rng, &[ // (Good::Terrain(BiomeKind::Snowland), 100.0_f32), // ]); - add_settlement(&mut env, "GrFoMo", 30.0, &[ + add_settlement(&mut env, "GrFoMo", 12000.0, &[ (Good::Terrain(BiomeKind::Grassland), 100.0_f32), (Good::Terrain(BiomeKind::Forest), 100.0_f32), (Good::Terrain(BiomeKind::Mountain), 10.0_f32), @@ -702,22 +697,24 @@ mod tests { // (Good::Terrain(BiomeKind::Mountain), 100.0_f32), // // (Good::CaveAccess, 100.0_f32), // ]); - // connect to neighbors + // connect to neighbors (one way) for i in 1..(env.index.sites.ids().count() as u64 - 1) { let previous = env.index.sites.recreate_id(i - 1); - let next = env.index.sites.recreate_id(i + 1); let center = env.index.sites.recreate_id(i); - center - .zip(previous) - .zip(next) - .map(|((center, previous), next)| { - env.index.sites[center].economy.add_neighbor(next, 1); - env.index.sites[next].economy.add_neighbor(center, 1); - env.index.sites[center].economy.add_neighbor(previous, 2); - env.index.sites[previous].economy.add_neighbor(center, 2); - }); + center.zip(previous).map(|(center, previous)| { + env.index.sites[center] + .economy + .add_neighbor(previous, i as usize); + env.index.sites[previous] + .economy + .add_neighbor(center, i as usize); + }); } crate::sim2::simulate(&mut env.index, &mut sim); show_economy(&env.index.sites); + // check population (shrinks if economy gets broken) + for (id, site) in env.index.sites.iter() { + assert!(site.economy.pop >= env.targets[&id]); + } } } diff --git a/world/src/site/economy/mod.rs b/world/src/site/economy/mod.rs index fdb0f29a8b..d1cf20171e 100644 --- a/world/src/site/economy/mod.rs +++ b/world/src/site/economy/mod.rs @@ -98,7 +98,8 @@ pub struct Economy { pub marginal_surplus: GoodMap, /// amount of wares not needed by the economy (helps with trade planning) pub unconsumed_stock: GoodMap, - /// Local availability of a good, 4.0 = starved, 2.0 = balanced, 0.1 = extra, NULL = way too much + /// Local availability of a good, 4.0 = starved, 2.0 = balanced, 0.1 = + /// extra, NULL = way too much // For some goods, such a goods without any supply, it doesn't make sense to talk about value pub values: GoodMap>, /// amount of goods exported/imported during the last cycle From 5f5ebeecfef263a42904965aa1e4113fbf332421 Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Sat, 2 Apr 2022 12:25:36 +0200 Subject: [PATCH 13/15] ignore expensive tests without much added value --- world/src/sim2/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/world/src/sim2/mod.rs b/world/src/sim2/mod.rs index a12675ac58..a524375131 100644 --- a/world/src/sim2/mod.rs +++ b/world/src/sim2/mod.rs @@ -462,6 +462,7 @@ mod tests { } #[test] + #[ignore] fn test_economy0() { init(); let threadpool = rayon::ThreadPoolBuilder::new().build().unwrap(); @@ -484,6 +485,7 @@ mod tests { } #[test] + #[ignore] fn test_economy1() { init(); let threadpool = rayon::ThreadPoolBuilder::new().build().unwrap(); From ab985b54ab346e838f10743bc6e2043360edccb0 Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Sat, 23 Apr 2022 00:08:20 +0200 Subject: [PATCH 14/15] initialize ERROR not INFO --- world/src/sim2/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world/src/sim2/mod.rs b/world/src/sim2/mod.rs index a524375131..1710d436b1 100644 --- a/world/src/sim2/mod.rs +++ b/world/src/sim2/mod.rs @@ -341,7 +341,7 @@ mod tests { // enable info! fn init() { FmtSubscriber::builder() - .with_max_level(Level::INFO) + .with_max_level(Level::ERROR) .with_env_filter(EnvFilter::from_default_env()) .try_init() .unwrap_or_default(); From 2ddd1cc0a7465bd55cc64059ceb3ba71f7a6ffb0 Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Sun, 24 Apr 2022 17:25:46 +0200 Subject: [PATCH 15/15] execute with local tracing environment sadly the level passed is ignored, and it warns about an invalid filter directive but RUST_LOG environment variables work as expected --- world/src/sim2/mod.rs | 445 ++++++++++++++++++++++-------------------- 1 file changed, 231 insertions(+), 214 deletions(-) diff --git a/world/src/sim2/mod.rs b/world/src/sim2/mod.rs index 1710d436b1..b92636de63 100644 --- a/world/src/sim2/mod.rs +++ b/world/src/sim2/mod.rs @@ -334,17 +334,20 @@ mod tests { use rand_chacha::ChaChaRng; use serde::{Deserialize, Serialize}; use std::convert::TryInto; - use tracing::{info, Level}; + use tracing::{info, Dispatch, Level}; use tracing_subscriber::{filter::EnvFilter, FmtSubscriber}; use vek::Vec2; - // enable info! - fn init() { - FmtSubscriber::builder() - .with_max_level(Level::ERROR) - .with_env_filter(EnvFilter::from_default_env()) - .try_init() - .unwrap_or_default(); + fn execute_with_tracing(level: Level, func: fn()) { + tracing::dispatcher::with_default( + &Dispatch::new( + FmtSubscriber::builder() + .with_max_level(level) + .with_env_filter(EnvFilter::from_default_env()) + .finish(), + ), + func, + ); } #[derive(Debug, Serialize, Deserialize)] @@ -461,157 +464,170 @@ mod tests { } } + /// output the economy of the currently active world + // this expensive test is for manual inspection, not to be run automated + // recommended command: cargo test test_economy0 -- --nocapture --ignored #[test] #[ignore] fn test_economy0() { - init(); - let threadpool = rayon::ThreadPoolBuilder::new().build().unwrap(); - info!("init"); - let seed = 59686; - let opts = sim::WorldOpts { - seed_elements: true, - world_file: sim::FileOpts::LoadAsset(sim::DEFAULT_WORLD_MAP.into()), - //sim::FileOpts::LoadAsset("world.map.economy_8x8".into()), - calendar: None, - }; - let mut index = crate::index::Index::new(seed); - info!("Index created"); - let mut sim = sim::WorldSim::generate(seed, opts, &threadpool); - info!("World loaded"); - let _civs = crate::civ::Civs::generate(seed, &mut sim, &mut index); - info!("Civs created"); - crate::sim2::simulate(&mut index, &mut sim); - show_economy(&index.sites); + execute_with_tracing(Level::INFO, || { + let threadpool = rayon::ThreadPoolBuilder::new().build().unwrap(); + info!("init"); + let seed = 59686; + let opts = sim::WorldOpts { + seed_elements: true, + world_file: sim::FileOpts::LoadAsset(sim::DEFAULT_WORLD_MAP.into()), + //sim::FileOpts::LoadAsset("world.map.economy_8x8".into()), + calendar: None, + }; + let mut index = crate::index::Index::new(seed); + info!("Index created"); + let mut sim = sim::WorldSim::generate(seed, opts, &threadpool); + info!("World loaded"); + let _civs = crate::civ::Civs::generate(seed, &mut sim, &mut index); + info!("Civs created"); + crate::sim2::simulate(&mut index, &mut sim); + show_economy(&index.sites); + }); } + /// output the economy of a small set of villages, loaded from ron + // this cheaper test is for manual inspection, not to be run automated #[test] #[ignore] fn test_economy1() { - init(); - let threadpool = rayon::ThreadPoolBuilder::new().build().unwrap(); - info!("init"); - let seed = 59686; - let opts = sim::WorldOpts { - seed_elements: true, - world_file: sim::FileOpts::LoadAsset(sim::DEFAULT_WORLD_MAP.into()), - //sim::FileOpts::LoadAsset("world.map.economy_8x8".into()), - calendar: None, - }; - let mut index = crate::index::Index::new(seed); - info!("Index created"); - let mut sim = sim::WorldSim::generate(seed, opts, &threadpool); - info!("World loaded"); - let regenerate_input = false; - if regenerate_input { - let _civs = crate::civ::Civs::generate(seed, &mut sim, &mut index); - info!("Civs created"); - let mut outarr: Vec = Vec::new(); - for i in index.sites.values() { - let resources: Vec = i - .economy - .natural_resources - .chunks_per_resource - .iter() - .map(|(good, a)| ResourcesSetup { - good: good.into(), - amount: *a * i.economy.natural_resources.average_yield_per_chunk[good], - }) - .collect(); - let neighbors = i - .economy - .neighbors - .iter() - .map(|j| (j.id.id(), j.travel_distance)) - .collect(); - let val = EconomySetup { - name: i.name().into(), - position: (i.get_origin().x, i.get_origin().y), - resources, - neighbors, - kind: match i.kind { - crate::site::SiteKind::Settlement(_) - | crate::site::SiteKind::Refactor(_) - | crate::site::SiteKind::CliffTown(_) => { - common::terrain::site::SitesKind::Settlement - }, - crate::site::SiteKind::Dungeon(_) => { - common::terrain::site::SitesKind::Dungeon - }, - crate::site::SiteKind::Castle(_) => { - common::terrain::site::SitesKind::Castle - }, - _ => common::terrain::site::SitesKind::Void, - }, - }; - outarr.push(val); - } - let pretty = ron::ser::PrettyConfig::new(); - if let Ok(result) = ron::ser::to_string_pretty(&outarr, pretty) { - info!("RON {}", result); - } - } else { - let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed)); - let ron_file = std::fs::File::open("economy_testinput2.ron") - .expect("economy_testinput2.ron not found"); - let econ_testinput: Vec = - ron::de::from_reader(ron_file).expect("economy_testinput2.ron parse error"); - for i in econ_testinput.iter() { - let wpos = Vec2 { - x: i.position.0, - y: i.position.1, - }; - // this should be a moderate compromise between regenerating the full world and - // loading on demand using the public API. There is no way to set - // the name, do we care? - let mut settlement = match i.kind { - common::terrain::site::SitesKind::Castle => crate::site::Site::castle( - crate::site::Castle::generate(wpos, None, &mut rng), - ), - common::terrain::site::SitesKind::Dungeon => crate::site::Site::dungeon( - crate::site2::Site::generate_dungeon(&crate::Land::empty(), &mut rng, wpos), - ), - // common::terrain::site::SitesKind::Settlement | - _ => crate::site::Site::settlement(crate::site::Settlement::generate( - wpos, None, &mut rng, - )), - }; - for g in i.resources.iter() { - //let c = sim::SimChunk::new(); - //settlement.economy.add_chunk(ch, distance_squared) - // bypass the API for now - settlement.economy.natural_resources.chunks_per_resource - [g.good.try_into().unwrap_or_default()] = g.amount; - settlement.economy.natural_resources.average_yield_per_chunk - [g.good.try_into().unwrap_or_default()] = 1.0; - } - index.sites.insert(settlement); - } - // we can't add these in the first loop as neighbors will refer to later sites - // (which aren't valid in the first loop) - for (i, e) in econ_testinput.iter().enumerate() { - if let Some(id) = index.sites.recreate_id(i as u64) { - let mut neighbors: Vec = e - .neighbors + execute_with_tracing(Level::INFO, || { + let threadpool = rayon::ThreadPoolBuilder::new().build().unwrap(); + info!("init"); + let seed = 59686; + let opts = sim::WorldOpts { + seed_elements: true, + world_file: sim::FileOpts::LoadAsset(sim::DEFAULT_WORLD_MAP.into()), + //sim::FileOpts::LoadAsset("world.map.economy_8x8".into()), + calendar: None, + }; + let mut index = crate::index::Index::new(seed); + info!("Index created"); + let mut sim = sim::WorldSim::generate(seed, opts, &threadpool); + info!("World loaded"); + let regenerate_input = false; + if regenerate_input { + let _civs = crate::civ::Civs::generate(seed, &mut sim, &mut index); + info!("Civs created"); + let mut outarr: Vec = Vec::new(); + for i in index.sites.values() { + let resources: Vec = i + .economy + .natural_resources + .chunks_per_resource .iter() - .flat_map(|(nid, dist)| index.sites.recreate_id(*nid).map(|i| (i, dist))) - .map(|(nid, dist)| crate::site::economy::NeighborInformation { - id: nid, - travel_distance: *dist, - last_values: GoodMap::from_default(0.0), - last_supplies: GoodMap::from_default(0.0), + .map(|(good, a)| ResourcesSetup { + good: good.into(), + amount: *a * i.economy.natural_resources.average_yield_per_chunk[good], }) .collect(); - index - .sites - .get_mut(id) + let neighbors = i .economy .neighbors - .append(&mut neighbors); + .iter() + .map(|j| (j.id.id(), j.travel_distance)) + .collect(); + let val = EconomySetup { + name: i.name().into(), + position: (i.get_origin().x, i.get_origin().y), + resources, + neighbors, + kind: match i.kind { + crate::site::SiteKind::Settlement(_) + | crate::site::SiteKind::Refactor(_) + | crate::site::SiteKind::CliffTown(_) => { + common::terrain::site::SitesKind::Settlement + }, + crate::site::SiteKind::Dungeon(_) => { + common::terrain::site::SitesKind::Dungeon + }, + crate::site::SiteKind::Castle(_) => { + common::terrain::site::SitesKind::Castle + }, + _ => common::terrain::site::SitesKind::Void, + }, + }; + outarr.push(val); + } + let pretty = ron::ser::PrettyConfig::new(); + if let Ok(result) = ron::ser::to_string_pretty(&outarr, pretty) { + info!("RON {}", result); + } + } else { + let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed)); + let ron_file = std::fs::File::open("economy_testinput2.ron") + .expect("economy_testinput2.ron not found"); + let econ_testinput: Vec = + ron::de::from_reader(ron_file).expect("economy_testinput2.ron parse error"); + for i in econ_testinput.iter() { + let wpos = Vec2 { + x: i.position.0, + y: i.position.1, + }; + // this should be a moderate compromise between regenerating the full world and + // loading on demand using the public API. There is no way to set + // the name, do we care? + let mut settlement = match i.kind { + common::terrain::site::SitesKind::Castle => crate::site::Site::castle( + crate::site::Castle::generate(wpos, None, &mut rng), + ), + common::terrain::site::SitesKind::Dungeon => { + crate::site::Site::dungeon(crate::site2::Site::generate_dungeon( + &crate::Land::empty(), + &mut rng, + wpos, + )) + }, + // common::terrain::site::SitesKind::Settlement | + _ => crate::site::Site::settlement(crate::site::Settlement::generate( + wpos, None, &mut rng, + )), + }; + for g in i.resources.iter() { + //let c = sim::SimChunk::new(); + //settlement.economy.add_chunk(ch, distance_squared) + // bypass the API for now + settlement.economy.natural_resources.chunks_per_resource + [g.good.try_into().unwrap_or_default()] = g.amount; + settlement.economy.natural_resources.average_yield_per_chunk + [g.good.try_into().unwrap_or_default()] = 1.0; + } + index.sites.insert(settlement); + } + // we can't add these in the first loop as neighbors will refer to later sites + // (which aren't valid in the first loop) + for (i, e) in econ_testinput.iter().enumerate() { + if let Some(id) = index.sites.recreate_id(i as u64) { + let mut neighbors: Vec = e + .neighbors + .iter() + .flat_map(|(nid, dist)| { + index.sites.recreate_id(*nid).map(|i| (i, dist)) + }) + .map(|(nid, dist)| crate::site::economy::NeighborInformation { + id: nid, + travel_distance: *dist, + last_values: GoodMap::from_default(0.0), + last_supplies: GoodMap::from_default(0.0), + }) + .collect(); + index + .sites + .get_mut(id) + .economy + .neighbors + .append(&mut neighbors); + } } } - } - crate::sim2::simulate(&mut index, &mut sim); - show_economy(&index.sites); + crate::sim2::simulate(&mut index, &mut sim); + show_economy(&index.sites); + }); } struct Simenv { @@ -621,7 +637,7 @@ mod tests { } #[test] - // test whether a site in moderate climate can survive on its own + /// test whether a site in moderate climate can survive on its own fn test_economy_moderate_standalone() { fn add_settlement( env: &mut Simenv, @@ -649,74 +665,75 @@ mod tests { id } - init(); - let threadpool = rayon::ThreadPoolBuilder::new().build().unwrap(); - info!("init"); - let seed = 59686; - let opts = sim::WorldOpts { - seed_elements: true, - world_file: sim::FileOpts::LoadAsset(sim::DEFAULT_WORLD_MAP.into()), - calendar: Default::default(), - }; - let index = crate::index::Index::new(seed); - info!("Index created"); - let mut sim = sim::WorldSim::generate(seed, opts, &threadpool); - info!("World loaded"); - let rng = ChaChaRng::from_seed(seed_expan::rng_state(seed)); - let mut env = Simenv { - index, - rng, - targets: hashbrown::HashMap::new(), - }; - add_settlement(&mut env, "Forest", 5000.0, &[( - Good::Terrain(BiomeKind::Forest), - 100.0_f32, - )]); - add_settlement(&mut env, "Grass", 900.0, &[( - Good::Terrain(BiomeKind::Grassland), - 100.0_f32, - )]); - add_settlement(&mut env, "Mountain", 3.0, &[( - Good::Terrain(BiomeKind::Mountain), - 100.0_f32, - )]); - // add_settlement(&mut env, "Desert", 19.0, &[( - // Good::Terrain(BiomeKind::Desert), - // 100.0_f32, - // )]); - // add_settlement(&mut index, &mut rng, &[ - // (Good::Terrain(BiomeKind::Jungle), 100.0_f32), - // ]); - // add_settlement(&mut index, &mut rng, &[ - // (Good::Terrain(BiomeKind::Snowland), 100.0_f32), - // ]); - add_settlement(&mut env, "GrFoMo", 12000.0, &[ - (Good::Terrain(BiomeKind::Grassland), 100.0_f32), - (Good::Terrain(BiomeKind::Forest), 100.0_f32), - (Good::Terrain(BiomeKind::Mountain), 10.0_f32), - ]); - // add_settlement(&mut env, "Mountain", 19.0, &[ - // (Good::Terrain(BiomeKind::Mountain), 100.0_f32), - // // (Good::CaveAccess, 100.0_f32), - // ]); - // connect to neighbors (one way) - for i in 1..(env.index.sites.ids().count() as u64 - 1) { - let previous = env.index.sites.recreate_id(i - 1); - let center = env.index.sites.recreate_id(i); - center.zip(previous).map(|(center, previous)| { - env.index.sites[center] - .economy - .add_neighbor(previous, i as usize); - env.index.sites[previous] - .economy - .add_neighbor(center, i as usize); - }); - } - crate::sim2::simulate(&mut env.index, &mut sim); - show_economy(&env.index.sites); - // check population (shrinks if economy gets broken) - for (id, site) in env.index.sites.iter() { - assert!(site.economy.pop >= env.targets[&id]); - } + execute_with_tracing(Level::ERROR, || { + let threadpool = rayon::ThreadPoolBuilder::new().build().unwrap(); + info!("init"); + let seed = 59686; + let opts = sim::WorldOpts { + seed_elements: true, + world_file: sim::FileOpts::LoadAsset(sim::DEFAULT_WORLD_MAP.into()), + calendar: Default::default(), + }; + let index = crate::index::Index::new(seed); + info!("Index created"); + let mut sim = sim::WorldSim::generate(seed, opts, &threadpool); + info!("World loaded"); + let rng = ChaChaRng::from_seed(seed_expan::rng_state(seed)); + let mut env = Simenv { + index, + rng, + targets: hashbrown::HashMap::new(), + }; + add_settlement(&mut env, "Forest", 5000.0, &[( + Good::Terrain(BiomeKind::Forest), + 100.0_f32, + )]); + add_settlement(&mut env, "Grass", 900.0, &[( + Good::Terrain(BiomeKind::Grassland), + 100.0_f32, + )]); + add_settlement(&mut env, "Mountain", 3.0, &[( + Good::Terrain(BiomeKind::Mountain), + 100.0_f32, + )]); + // add_settlement(&mut env, "Desert", 19.0, &[( + // Good::Terrain(BiomeKind::Desert), + // 100.0_f32, + // )]); + // add_settlement(&mut index, &mut rng, &[ + // (Good::Terrain(BiomeKind::Jungle), 100.0_f32), + // ]); + // add_settlement(&mut index, &mut rng, &[ + // (Good::Terrain(BiomeKind::Snowland), 100.0_f32), + // ]); + add_settlement(&mut env, "GrFoMo", 12000.0, &[ + (Good::Terrain(BiomeKind::Grassland), 100.0_f32), + (Good::Terrain(BiomeKind::Forest), 100.0_f32), + (Good::Terrain(BiomeKind::Mountain), 10.0_f32), + ]); + // add_settlement(&mut env, "Mountain", 19.0, &[ + // (Good::Terrain(BiomeKind::Mountain), 100.0_f32), + // // (Good::CaveAccess, 100.0_f32), + // ]); + // connect to neighbors (one way) + for i in 1..(env.index.sites.ids().count() as u64 - 1) { + let previous = env.index.sites.recreate_id(i - 1); + let center = env.index.sites.recreate_id(i); + center.zip(previous).map(|(center, previous)| { + env.index.sites[center] + .economy + .add_neighbor(previous, i as usize); + env.index.sites[previous] + .economy + .add_neighbor(center, i as usize); + }); + } + crate::sim2::simulate(&mut env.index, &mut sim); + show_economy(&env.index.sites); + // check population (shrinks if economy gets broken) + for (id, site) in env.index.sites.iter() { + assert!(site.economy.pop >= env.targets[&id]); + } + }); } }