2020-03-28 18:16:19 +00:00
|
|
|
mod econ;
|
|
|
|
|
2020-03-29 19:48:51 +00:00
|
|
|
use std::{
|
|
|
|
ops::Range,
|
|
|
|
hash::Hash,
|
2020-03-30 16:46:44 +00:00
|
|
|
fmt,
|
2020-03-29 19:48:51 +00:00
|
|
|
};
|
2020-03-27 13:16:02 +00:00
|
|
|
use hashbrown::{HashMap, HashSet};
|
|
|
|
use vek::*;
|
|
|
|
use rand::prelude::*;
|
|
|
|
use common::{
|
|
|
|
terrain::TerrainChunkSize,
|
|
|
|
vol::RectVolSize,
|
|
|
|
store::{Id, Store},
|
|
|
|
path::Path,
|
|
|
|
astar::Astar,
|
|
|
|
};
|
2020-03-27 23:06:23 +00:00
|
|
|
use crate::sim::{WorldSim, SimChunk};
|
2020-03-27 13:16:02 +00:00
|
|
|
|
|
|
|
const CARDINALS: [Vec2<i32>; 4] = [
|
|
|
|
Vec2::new(1, 0),
|
|
|
|
Vec2::new(-1, 0),
|
|
|
|
Vec2::new(0, 1),
|
|
|
|
Vec2::new(0, -1),
|
|
|
|
];
|
|
|
|
|
|
|
|
const DIAGONALS: [Vec2<i32>; 8] = [
|
|
|
|
Vec2::new(1, 0),
|
|
|
|
Vec2::new(1, 1),
|
|
|
|
Vec2::new(-1, 0),
|
|
|
|
Vec2::new(-1, 1),
|
|
|
|
Vec2::new(0, 1),
|
|
|
|
Vec2::new(1, -1),
|
|
|
|
Vec2::new(0, -1),
|
|
|
|
Vec2::new(-1, -1),
|
|
|
|
];
|
|
|
|
|
|
|
|
fn attempt<T>(max_iters: usize, mut f: impl FnMut() -> Option<T>) -> Option<T> {
|
|
|
|
(0..max_iters).find_map(|_| f())
|
|
|
|
}
|
|
|
|
|
2020-03-30 16:46:44 +00:00
|
|
|
const INITIAL_CIV_COUNT: usize = 16;
|
2020-03-27 13:16:02 +00:00
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
pub struct Civs {
|
|
|
|
civs: Store<Civ>,
|
|
|
|
places: Store<Place>,
|
2020-03-27 23:06:23 +00:00
|
|
|
|
2020-03-27 18:52:28 +00:00
|
|
|
tracks: Store<Track>,
|
2020-03-27 23:06:23 +00:00
|
|
|
track_map: HashMap<Id<Site>, HashMap<Id<Site>, Id<Track>>>,
|
|
|
|
|
|
|
|
sites: Store<Site>,
|
2020-03-27 13:16:02 +00:00
|
|
|
}
|
|
|
|
|
2020-03-28 18:16:19 +00:00
|
|
|
pub struct GenCtx<'a, R: Rng> {
|
2020-03-27 13:16:02 +00:00
|
|
|
sim: &'a mut WorldSim,
|
|
|
|
rng: &'a mut R,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Civs {
|
|
|
|
pub fn generate(seed: u32, sim: &mut WorldSim) -> Self {
|
|
|
|
let mut this = Self::default();
|
|
|
|
let mut rng = sim.rng.clone();
|
|
|
|
let mut ctx = GenCtx { sim, rng: &mut rng };
|
|
|
|
|
|
|
|
for _ in 0..INITIAL_CIV_COUNT {
|
2020-03-27 23:06:23 +00:00
|
|
|
println!("Creating civilisation...");
|
|
|
|
if let None = this.birth_civ(&mut ctx) {
|
|
|
|
println!("Failed to find starting site for civilisation.");
|
2020-03-27 13:16:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-27 23:06:23 +00:00
|
|
|
// Tick
|
2020-03-28 18:16:19 +00:00
|
|
|
const SIM_YEARS: usize = 1000;
|
2020-03-27 23:06:23 +00:00
|
|
|
for _ in 0..SIM_YEARS {
|
2020-03-28 18:16:19 +00:00
|
|
|
this.tick(&mut ctx, 1.0);
|
2020-03-27 23:06:23 +00:00
|
|
|
}
|
|
|
|
|
2020-03-27 13:16:02 +00:00
|
|
|
// Temporary!
|
2020-03-27 18:52:28 +00:00
|
|
|
for track in this.tracks.iter() {
|
|
|
|
for loc in track.path.iter() {
|
2020-03-27 13:16:02 +00:00
|
|
|
sim.get_mut(*loc).unwrap().place = Some(this.civs.iter().next().unwrap().homeland);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-27 23:06:23 +00:00
|
|
|
this.display_info();
|
|
|
|
|
2020-03-27 13:16:02 +00:00
|
|
|
this
|
|
|
|
}
|
|
|
|
|
2020-03-27 23:06:23 +00:00
|
|
|
pub fn place(&self, id: Id<Place>) -> &Place { self.places.get(id) }
|
|
|
|
|
2020-03-28 18:16:19 +00:00
|
|
|
pub fn sites(&self) -> impl Iterator<Item=&Site> + '_ {
|
|
|
|
self.sites.iter()
|
|
|
|
}
|
|
|
|
|
2020-03-27 23:06:23 +00:00
|
|
|
fn display_info(&self) {
|
|
|
|
for (id, civ) in self.civs.iter_ids() {
|
|
|
|
println!("# Civilisation {:?}", id);
|
|
|
|
println!("Name: {}", "<unnamed>");
|
|
|
|
println!("Homeland: {:#?}", self.places.get(civ.homeland));
|
|
|
|
}
|
|
|
|
|
|
|
|
for (id, site) in self.sites.iter_ids() {
|
|
|
|
println!("# Site {:?}", id);
|
2020-03-28 18:16:19 +00:00
|
|
|
println!("{:#?}", site);
|
2020-03-27 23:06:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-27 18:52:28 +00:00
|
|
|
/// Return the direct track between two places
|
2020-03-27 23:06:23 +00:00
|
|
|
fn track_between(&self, a: Id<Site>, b: Id<Site>) -> Option<Id<Track>> {
|
2020-03-27 18:52:28 +00:00
|
|
|
self.track_map
|
|
|
|
.get(&a)
|
|
|
|
.and_then(|dests| dests.get(&b))
|
|
|
|
.or_else(|| self.track_map
|
|
|
|
.get(&b)
|
|
|
|
.and_then(|dests| dests.get(&a)))
|
|
|
|
.copied()
|
|
|
|
}
|
|
|
|
|
2020-03-27 23:06:23 +00:00
|
|
|
/// Return an iterator over a site's neighbors
|
|
|
|
fn neighbors(&self, site: Id<Site>) -> impl Iterator<Item=Id<Site>> + '_ {
|
|
|
|
let to = self.track_map.get(&site).map(|dests| dests.keys()).into_iter().flatten();
|
|
|
|
let fro = self.track_map.iter().filter(move |(_, dests)| dests.contains_key(&site)).map(|(p, _)| p);
|
|
|
|
to.chain(fro).filter(move |p| **p != site).copied()
|
|
|
|
}
|
|
|
|
|
2020-03-27 18:52:28 +00:00
|
|
|
/// Find the cheapest route between two places
|
2020-03-27 23:06:23 +00:00
|
|
|
fn route_between(&self, a: Id<Site>, b: Id<Site>) -> Option<(Path<Id<Site>>, f32)> {
|
|
|
|
let heuristic = move |p: &Id<Site>| (self.sites.get(*p).center.distance_squared(self.sites.get(b).center) as f32).sqrt();
|
|
|
|
let neighbors = |p: &Id<Site>| self.neighbors(*p);
|
|
|
|
let transition = |a: &Id<Site>, b: &Id<Site>| self.tracks.get(self.track_between(*a, *b).unwrap()).cost;
|
|
|
|
let satisfied = |p: &Id<Site>| *p == b;
|
2020-03-27 18:52:28 +00:00
|
|
|
let mut astar = Astar::new(100, a, heuristic);
|
|
|
|
astar
|
|
|
|
.poll(100, heuristic, neighbors, transition, satisfied)
|
|
|
|
.into_path()
|
|
|
|
.and_then(|path| astar.get_cheapest_cost().map(|cost| (path, cost)))
|
|
|
|
}
|
|
|
|
|
2020-03-27 13:16:02 +00:00
|
|
|
fn birth_civ(&mut self, ctx: &mut GenCtx<impl Rng>) -> Option<Id<Civ>> {
|
2020-03-27 23:06:23 +00:00
|
|
|
let site = attempt(5, || {
|
2020-03-27 13:16:02 +00:00
|
|
|
let loc = find_site_loc(ctx, None)?;
|
2020-03-29 19:48:51 +00:00
|
|
|
self.establish_site(ctx, loc)
|
2020-03-27 13:16:02 +00:00
|
|
|
})?;
|
|
|
|
|
|
|
|
let civ = self.civs.insert(Civ {
|
2020-03-27 23:06:23 +00:00
|
|
|
capital: site,
|
|
|
|
homeland: self.sites.get(site).place,
|
2020-03-27 13:16:02 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
Some(civ)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn establish_place(&mut self, ctx: &mut GenCtx<impl Rng>, loc: Vec2<i32>, area: Range<usize>) -> Option<Id<Place>> {
|
|
|
|
let mut dead = HashSet::new();
|
|
|
|
let mut alive = HashSet::new();
|
|
|
|
alive.insert(loc);
|
|
|
|
|
|
|
|
// Fill the surrounding area
|
|
|
|
while let Some(cloc) = alive.iter().choose(ctx.rng).copied() {
|
|
|
|
for dir in CARDINALS.iter() {
|
|
|
|
if site_in_dir(&ctx.sim, cloc, *dir) {
|
|
|
|
let rloc = cloc + *dir;
|
|
|
|
if !dead.contains(&rloc) && ctx.sim.get(rloc).map(|c| c.place.is_none()).unwrap_or(false) {
|
|
|
|
alive.insert(rloc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
alive.remove(&cloc);
|
|
|
|
dead.insert(cloc);
|
|
|
|
|
|
|
|
if dead.len() + alive.len() >= area.end {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Make sure the place is large enough
|
|
|
|
if dead.len() + alive.len() <= area.start {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
2020-03-27 18:52:28 +00:00
|
|
|
let place = self.places.insert(Place {
|
|
|
|
center: loc,
|
2020-03-27 23:06:23 +00:00
|
|
|
nat_res: NaturalResources::default(),
|
|
|
|
});
|
|
|
|
|
|
|
|
// Write place to map
|
|
|
|
for cell in dead.union(&alive) {
|
|
|
|
if let Some(chunk) = ctx.sim.get_mut(*cell) {
|
|
|
|
chunk.place = Some(place);
|
|
|
|
self.places.get_mut(place).nat_res.include_chunk(ctx, *cell);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Some(place)
|
|
|
|
}
|
|
|
|
|
2020-03-29 19:48:51 +00:00
|
|
|
fn establish_site(&mut self, ctx: &mut GenCtx<impl Rng>, loc: Vec2<i32>) -> Option<Id<Site>> {
|
2020-03-27 23:06:23 +00:00
|
|
|
const SITE_AREA: Range<usize> = 64..256;
|
|
|
|
|
|
|
|
let place = match ctx.sim.get(loc).and_then(|site| site.place) {
|
|
|
|
Some(place) => place,
|
|
|
|
None => self.establish_place(ctx, loc, SITE_AREA)?,
|
|
|
|
};
|
|
|
|
|
|
|
|
let site = self.sites.insert(Site {
|
2020-03-29 19:48:51 +00:00
|
|
|
kind: SiteKind::Settlement,
|
2020-03-27 23:06:23 +00:00
|
|
|
center: loc,
|
|
|
|
place: place,
|
2020-03-29 19:48:51 +00:00
|
|
|
|
|
|
|
population: 24.0,
|
2020-03-30 16:46:44 +00:00
|
|
|
|
|
|
|
stocks: Stocks::from_default(100.0),
|
2020-03-30 22:45:41 +00:00
|
|
|
surplus: Stocks::from_default(0.0),
|
2020-03-30 16:46:44 +00:00
|
|
|
values: Stocks::from_default(None),
|
|
|
|
|
|
|
|
labors: MapVec::from_default(0.01),
|
|
|
|
yields: MapVec::from_default(1.0),
|
|
|
|
|
2020-03-30 22:45:41 +00:00
|
|
|
trade_states: Stocks::default(),
|
|
|
|
coin: 1000.0,
|
2020-03-27 18:52:28 +00:00
|
|
|
});
|
|
|
|
|
2020-03-27 13:16:02 +00:00
|
|
|
// Find neighbors
|
2020-03-27 18:52:28 +00:00
|
|
|
const MAX_NEIGHBOR_DISTANCE: f32 = 250.0;
|
2020-03-27 23:06:23 +00:00
|
|
|
let mut nearby = self.sites
|
2020-03-27 13:16:02 +00:00
|
|
|
.iter_ids()
|
|
|
|
.map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt()))
|
|
|
|
.filter(|(p, dist)| *dist < MAX_NEIGHBOR_DISTANCE)
|
|
|
|
.collect::<Vec<_>>();
|
2020-03-27 18:52:28 +00:00
|
|
|
nearby.sort_by_key(|(_, dist)| *dist as i32);
|
|
|
|
|
2020-03-29 19:48:51 +00:00
|
|
|
for (nearby, _) in nearby.into_iter().take(5) {
|
2020-03-27 18:52:28 +00:00
|
|
|
// Find a novel path
|
2020-03-27 23:06:23 +00:00
|
|
|
if let Some((path, cost)) = find_path(ctx, loc, self.sites.get(nearby).center) {
|
2020-03-27 18:52:28 +00:00
|
|
|
// Find a path using existing paths
|
|
|
|
if self
|
2020-03-27 23:06:23 +00:00
|
|
|
.route_between(site, nearby)
|
2020-03-27 18:52:28 +00:00
|
|
|
// If the novel path isn't efficient compared to existing routes, don't use it
|
|
|
|
.filter(|(_, route_cost)| *route_cost < cost * 3.0)
|
|
|
|
.is_none()
|
|
|
|
{
|
|
|
|
let track = self.tracks.insert(Track {
|
|
|
|
cost,
|
|
|
|
path,
|
|
|
|
});
|
|
|
|
self.track_map
|
2020-03-27 23:06:23 +00:00
|
|
|
.entry(site)
|
2020-03-27 18:52:28 +00:00
|
|
|
.or_default()
|
|
|
|
.insert(nearby, track);
|
|
|
|
}
|
|
|
|
}
|
2020-03-27 13:16:02 +00:00
|
|
|
}
|
|
|
|
|
2020-03-27 23:06:23 +00:00
|
|
|
Some(site)
|
|
|
|
}
|
|
|
|
|
2020-03-28 18:16:19 +00:00
|
|
|
fn tick(&mut self, ctx: &mut GenCtx<impl Rng>, years: f32) {
|
2020-03-30 16:46:44 +00:00
|
|
|
println!("Tick!");
|
2020-03-28 18:16:19 +00:00
|
|
|
for site in self.sites.iter_mut() {
|
2020-03-30 16:46:44 +00:00
|
|
|
site.simulate(years, &self.places.get(site.place).nat_res);
|
2020-03-28 18:16:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Trade stocks
|
2020-03-30 22:45:41 +00:00
|
|
|
let mut stocks = TRADE_STOCKS;
|
|
|
|
stocks.shuffle(ctx.rng); // Give each stock a chance to be traded first
|
|
|
|
for stock in stocks.iter().copied() {
|
|
|
|
let mut sell_orders = self.sites
|
|
|
|
.iter_ids()
|
|
|
|
.map(|(id, site)| (id, {
|
|
|
|
let total_value = site.values.iter().map(|(_, v)| (*v).unwrap_or(0.0)).sum::<f32>();
|
|
|
|
let quantity = if let Some(val) = site.values[stock] {
|
|
|
|
((total_value / val) * site.surplus[stock].max(0.0)).min(site.stocks[stock])
|
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
};
|
|
|
|
econ::SellOrder {
|
|
|
|
quantity,
|
|
|
|
price: site.trade_states[stock].sell_belief.choose_price(ctx) * 1.25, // Trade cost
|
|
|
|
q_sold: 0.0,
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
.filter(|(_, order)| order.quantity > 0.0)
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
let mut sites = self.sites
|
|
|
|
.ids()
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
sites.shuffle(ctx.rng); // Give all sites a chance to buy first
|
|
|
|
for site in sites {
|
|
|
|
let (max_spend, max_price) = {
|
|
|
|
let site = self.sites.get(site);
|
|
|
|
let budget = site.coin * 0.5;
|
|
|
|
let total_value = site.values.iter().map(|(_, v)| (*v).unwrap_or(0.0)).sum::<f32>();
|
|
|
|
(
|
|
|
|
(site.values[stock].unwrap_or(0.1) / total_value * budget).min(budget),
|
|
|
|
site.trade_states[stock].buy_belief.price,
|
|
|
|
)
|
|
|
|
};
|
|
|
|
let (quantity, spent) = econ::buy_units(ctx, sell_orders
|
|
|
|
.iter_mut()
|
|
|
|
.filter(|(id, _)| site != *id && self.track_between(site, *id).is_some())
|
|
|
|
.map(|(_, order)| order),
|
|
|
|
1000000.0, // Max quantity TODO
|
|
|
|
1000000.0, // Max price TODO
|
|
|
|
max_spend,
|
|
|
|
);
|
|
|
|
let mut site = self.sites.get_mut(site);
|
|
|
|
site.coin -= spent;
|
|
|
|
if quantity > 0.0 {
|
|
|
|
site.stocks[stock] += quantity;
|
|
|
|
site.trade_states[stock].buy_belief.update_buyer(years, spent / quantity);
|
|
|
|
println!("Belief: {:?}", site.trade_states[stock].buy_belief);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (site, order) in sell_orders {
|
|
|
|
let mut site = self.sites.get_mut(site);
|
|
|
|
site.coin += order.q_sold * order.price;
|
|
|
|
if order.q_sold > 0.0 {
|
|
|
|
site.stocks[stock] -= order.q_sold;
|
|
|
|
site.trade_states[stock].sell_belief.update_seller(order.q_sold / order.quantity);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-03-27 13:16:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Attempt to find a path between two locations
|
2020-03-27 18:52:28 +00:00
|
|
|
fn find_path(ctx: &mut GenCtx<impl Rng>, a: Vec2<i32>, b: Vec2<i32>) -> Option<(Path<Vec2<i32>>, f32)> {
|
2020-03-27 13:16:02 +00:00
|
|
|
let sim = &ctx.sim;
|
|
|
|
let heuristic = move |l: &Vec2<i32>| (l.distance_squared(b) as f32).sqrt();
|
|
|
|
let neighbors = |l: &Vec2<i32>| {
|
|
|
|
let l = *l;
|
|
|
|
DIAGONALS.iter().filter(move |dir| walk_in_dir(sim, l, **dir).is_some()).map(move |dir| l + *dir)
|
|
|
|
};
|
|
|
|
let transition = |a: &Vec2<i32>, b: &Vec2<i32>| 1.0 + walk_in_dir(sim, *a, *b - *a).unwrap_or(10000.0);
|
|
|
|
let satisfied = |l: &Vec2<i32>| *l == b;
|
2020-03-27 18:52:28 +00:00
|
|
|
let mut astar = Astar::new(20000, a, heuristic);
|
|
|
|
astar
|
|
|
|
.poll(20000, heuristic, neighbors, transition, satisfied)
|
2020-03-27 13:16:02 +00:00
|
|
|
.into_path()
|
2020-03-27 18:52:28 +00:00
|
|
|
.and_then(|path| astar.get_cheapest_cost().map(|cost| (path, cost)))
|
2020-03-27 13:16:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Return true if travel between a location and a chunk next to it is permitted (TODO: by whom?)
|
|
|
|
fn walk_in_dir(sim: &WorldSim, a: Vec2<i32>, dir: Vec2<i32>) -> Option<f32> {
|
|
|
|
if loc_suitable_for_walking(sim, a) &&
|
|
|
|
loc_suitable_for_walking(sim, a + dir)
|
|
|
|
{
|
|
|
|
let a_alt = sim.get(a)?.alt;
|
|
|
|
let b_alt = sim.get(a + dir)?.alt;
|
2020-03-27 23:06:23 +00:00
|
|
|
Some((b_alt - a_alt).abs() / 2.5)
|
2020-03-27 13:16:02 +00:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return true if a position is suitable for walking on
|
|
|
|
fn loc_suitable_for_walking(sim: &WorldSim, loc: Vec2<i32>) -> bool {
|
|
|
|
if let Some(chunk) = sim.get(loc) {
|
|
|
|
!chunk.river.is_ocean() && !chunk.river.is_lake()
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return true if a site could be constructed between a location and a chunk next to it is permitted (TODO: by whom?)
|
|
|
|
fn site_in_dir(sim: &WorldSim, a: Vec2<i32>, dir: Vec2<i32>) -> bool {
|
|
|
|
loc_suitable_for_site(sim, a) &&
|
|
|
|
loc_suitable_for_site(sim, a + dir)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return true if a position is suitable for site construction (TODO: criteria?)
|
|
|
|
fn loc_suitable_for_site(sim: &WorldSim, loc: Vec2<i32>) -> bool {
|
|
|
|
if let Some(chunk) = sim.get(loc) {
|
2020-03-27 23:06:23 +00:00
|
|
|
!chunk.river.is_ocean() &&
|
|
|
|
!chunk.river.is_lake() &&
|
2020-03-27 13:16:02 +00:00
|
|
|
sim.get_gradient_approx(loc).map(|grad| grad < 1.0).unwrap_or(false)
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Attempt to search for a location that's suitable for site construction
|
|
|
|
fn find_site_loc(ctx: &mut GenCtx<impl Rng>, near: Option<(Vec2<i32>, f32)>) -> Option<Vec2<i32>> {
|
|
|
|
const MAX_ATTEMPTS: usize = 100;
|
|
|
|
let mut loc = None;
|
|
|
|
for _ in 0..MAX_ATTEMPTS {
|
|
|
|
let test_loc = loc.unwrap_or_else(|| match near {
|
|
|
|
Some((origin, dist)) => origin + (Vec2::new(
|
|
|
|
ctx.rng.gen_range(-1.0, 1.0),
|
|
|
|
ctx.rng.gen_range(-1.0, 1.0),
|
|
|
|
).try_normalized().unwrap_or(Vec2::zero()) * ctx.rng.gen::<f32>() * dist).map(|e| e as i32),
|
|
|
|
None => Vec2::new(
|
|
|
|
ctx.rng.gen_range(0, ctx.sim.get_size().x as i32),
|
|
|
|
ctx.rng.gen_range(0, ctx.sim.get_size().y as i32),
|
|
|
|
),
|
|
|
|
});
|
|
|
|
|
|
|
|
if loc_suitable_for_site(&ctx.sim, test_loc) {
|
|
|
|
return Some(test_loc);
|
|
|
|
}
|
|
|
|
|
|
|
|
loc = ctx.sim.get(test_loc).and_then(|c| Some(c.downhill?.map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| {
|
|
|
|
e / (sz as i32)
|
|
|
|
})));
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Civ {
|
2020-03-27 23:06:23 +00:00
|
|
|
capital: Id<Site>,
|
2020-03-27 13:16:02 +00:00
|
|
|
homeland: Id<Place>,
|
|
|
|
}
|
|
|
|
|
2020-03-27 23:06:23 +00:00
|
|
|
#[derive(Debug)]
|
2020-03-27 13:16:02 +00:00
|
|
|
pub struct Place {
|
|
|
|
center: Vec2<i32>,
|
2020-03-27 23:06:23 +00:00
|
|
|
nat_res: NaturalResources,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Productive capacity per year
|
|
|
|
#[derive(Default, Debug)]
|
|
|
|
pub struct NaturalResources {
|
|
|
|
wood: f32,
|
2020-03-30 16:46:44 +00:00
|
|
|
rock: f32,
|
2020-03-27 23:06:23 +00:00
|
|
|
river: f32,
|
|
|
|
farmland: f32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl NaturalResources {
|
|
|
|
fn include_chunk(&mut self, ctx: &mut GenCtx<impl Rng>, loc: Vec2<i32>) {
|
|
|
|
let chunk = if let Some(chunk) = ctx.sim.get(loc) { chunk } else { return };
|
|
|
|
|
|
|
|
self.wood += chunk.tree_density;
|
2020-03-30 16:46:44 +00:00
|
|
|
self.rock += chunk.rockiness;
|
2020-03-29 19:48:51 +00:00
|
|
|
self.river += if chunk.river.is_river() { 5.0 } else { 0.0 };
|
2020-03-27 23:06:23 +00:00
|
|
|
self.farmland += if
|
|
|
|
chunk.humidity > 0.35 &&
|
|
|
|
chunk.temp > -0.3 && chunk.temp < 0.75 &&
|
|
|
|
chunk.chaos < 0.5 &&
|
|
|
|
ctx.sim.get_gradient_approx(loc).map(|grad| grad < 0.7).unwrap_or(false)
|
|
|
|
{ 1.0 } else { 0.0 };
|
|
|
|
}
|
2020-03-27 13:16:02 +00:00
|
|
|
}
|
|
|
|
|
2020-03-27 18:52:28 +00:00
|
|
|
pub struct Track {
|
|
|
|
/// Cost of using this track relative to other paths. This cost is an arbitrary unit and
|
|
|
|
/// doesn't make sense unless compared to other track costs.
|
|
|
|
cost: f32,
|
2020-03-27 13:16:02 +00:00
|
|
|
path: Path<Vec2<i32>>,
|
|
|
|
}
|
2020-03-27 23:06:23 +00:00
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Site {
|
|
|
|
kind: SiteKind,
|
|
|
|
center: Vec2<i32>,
|
2020-03-28 18:16:19 +00:00
|
|
|
pub place: Id<Place>,
|
|
|
|
|
|
|
|
population: f32,
|
2020-03-30 16:46:44 +00:00
|
|
|
|
|
|
|
// Total amount of each stock
|
2020-03-28 18:16:19 +00:00
|
|
|
stocks: Stocks<f32>,
|
2020-03-30 22:45:41 +00:00
|
|
|
// Surplus stock compared to demand orders
|
|
|
|
surplus: Stocks<f32>,
|
2020-03-30 16:46:44 +00:00
|
|
|
// For some goods, such a goods without any supply, it doesn't make sense to talk about value
|
|
|
|
values: Stocks<Option<f32>>,
|
|
|
|
|
|
|
|
// Proportion of individuals dedicated to an industry
|
|
|
|
labors: MapVec<Occupation, f32>,
|
|
|
|
// Per worker, per year, of their output good
|
|
|
|
yields: MapVec<Occupation, f32>,
|
|
|
|
|
2020-03-30 22:45:41 +00:00
|
|
|
trade_states: Stocks<TradeState>,
|
|
|
|
coin: f32,
|
2020-03-30 16:46:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for Site {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
match self.kind {
|
|
|
|
SiteKind::Settlement => writeln!(f, "Settlement")?,
|
|
|
|
}
|
|
|
|
writeln!(f, "- population: {}", self.population.floor() as u32)?;
|
2020-03-30 22:45:41 +00:00
|
|
|
writeln!(f, "- coin: {}", self.coin.floor() as u32)?;
|
2020-03-30 16:46:44 +00:00
|
|
|
writeln!(f, "Stocks")?;
|
|
|
|
for (stock, q) in self.stocks.iter() {
|
|
|
|
writeln!(f, "- {}: {}", stock, q.floor())?;
|
|
|
|
}
|
|
|
|
writeln!(f, "Prices")?;
|
|
|
|
for (stock, v) in self.values.iter() {
|
|
|
|
writeln!(f, "- {}: {}", stock, v.map(|x| x.to_string()).unwrap_or_else(|| "N/A".to_string()))?;
|
|
|
|
}
|
|
|
|
writeln!(f, "Laborers")?;
|
|
|
|
for (labor, n) in self.labors.iter() {
|
|
|
|
writeln!(f, "- {}: {}", labor, (*n * self.population).floor() as u32)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-03-27 23:06:23 +00:00
|
|
|
}
|
|
|
|
|
2020-03-29 19:48:51 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum SiteKind {
|
|
|
|
Settlement,
|
|
|
|
}
|
2020-03-28 18:16:19 +00:00
|
|
|
|
2020-03-29 19:48:51 +00:00
|
|
|
impl Site {
|
2020-03-30 16:46:44 +00:00
|
|
|
pub fn simulate(&mut self, years: f32, nat_res: &NaturalResources) {
|
|
|
|
// Insert natural resources into the economy
|
|
|
|
if self.stocks[FISH] < nat_res.river {
|
|
|
|
self.stocks[FISH] = nat_res.river;
|
|
|
|
}
|
|
|
|
if self.stocks[WHEAT] < nat_res.farmland {
|
|
|
|
self.stocks[WHEAT] = nat_res.farmland;
|
|
|
|
}
|
|
|
|
if self.stocks[LOGS] < nat_res.wood {
|
|
|
|
self.stocks[LOGS] = nat_res.wood;
|
|
|
|
}
|
|
|
|
if self.stocks[GAME] < nat_res.wood {
|
|
|
|
self.stocks[GAME] = nat_res.wood;
|
|
|
|
}
|
|
|
|
if self.stocks[ROCK] < nat_res.rock {
|
|
|
|
self.stocks[ROCK] = nat_res.rock;
|
2020-03-29 19:48:51 +00:00
|
|
|
}
|
|
|
|
|
2020-03-30 16:46:44 +00:00
|
|
|
let orders = vec![
|
|
|
|
(None, vec![(FOOD, 0.25)]),
|
|
|
|
(Some(COOK), vec![(FLOUR, 6.5), (MEAT, 1.5)]),
|
|
|
|
(Some(LUMBERJACK), vec![(LOGS, 4.5)]),
|
|
|
|
(Some(MINER), vec![(ROCK, 7.5)]),
|
|
|
|
(Some(FISHER), vec![(FISH, 4.0)]),
|
|
|
|
(Some(HUNTER), vec![(GAME, 4.0)]),
|
|
|
|
(Some(FARMER), vec![(WHEAT, 4.0)]),
|
|
|
|
]
|
|
|
|
.into_iter()
|
|
|
|
.collect::<HashMap<_, Vec<(Stock, f32)>>>();
|
|
|
|
|
|
|
|
let mut demand = Stocks::from_default(0.0);
|
|
|
|
for (labor, orders) in &orders {
|
|
|
|
let scale = if let Some(labor) = labor { self.labors[*labor] } else { 1.0 } * self.population;
|
|
|
|
for (stock, amount) in orders {
|
|
|
|
debug_assert!(!amount.is_nan(), "{:?}, {}", labor, stock);
|
|
|
|
debug_assert!(!scale.is_nan(), "{:?}, {}, {}", labor, stock, self.population);
|
|
|
|
demand[*stock] += *amount * scale;
|
|
|
|
}
|
|
|
|
}
|
2020-03-28 18:16:19 +00:00
|
|
|
|
2020-03-30 22:45:41 +00:00
|
|
|
self.surplus = demand.clone().map(|stock, tgt| {
|
2020-03-30 16:46:44 +00:00
|
|
|
debug_assert!(!self.stocks[stock].is_nan());
|
|
|
|
debug_assert!(!demand[stock].is_nan());
|
|
|
|
self.stocks[stock] - demand[stock]
|
|
|
|
});
|
2020-03-27 23:06:23 +00:00
|
|
|
|
2020-03-30 16:46:44 +00:00
|
|
|
// Update values according to the surplus of each stock
|
2020-03-30 22:45:41 +00:00
|
|
|
let values = &mut self.values;
|
|
|
|
self.surplus.iter().for_each(|(stock, surplus)| {
|
|
|
|
let val = 3.5f32.powf(-*surplus / demand[stock]);
|
|
|
|
values[stock] = if val > 0.001 && val < 1000.0 { Some(val) } else { None };
|
2020-03-30 16:46:44 +00:00
|
|
|
});
|
2020-03-28 18:16:19 +00:00
|
|
|
|
2020-03-30 16:46:44 +00:00
|
|
|
// Per labourer, per year
|
|
|
|
let production = Stocks::from_list(&[
|
|
|
|
(FARMER, (FLOUR, 2.0)),
|
|
|
|
(LUMBERJACK, (WOOD, 1.5)),
|
|
|
|
(MINER, (STONE, 0.6)),
|
|
|
|
(FISHER, (MEAT, 3.0)),
|
|
|
|
(HUNTER, (MEAT, 0.5)),
|
|
|
|
(COOK, (FOOD, 8.0)),
|
2020-03-28 18:16:19 +00:00
|
|
|
]);
|
|
|
|
|
2020-03-30 16:46:44 +00:00
|
|
|
let population = self.population;
|
2020-03-29 19:48:51 +00:00
|
|
|
|
2020-03-30 16:46:44 +00:00
|
|
|
// Redistribute workforce according to relative good values
|
|
|
|
let labor_ratios = production.clone().map(|labor, (output_stock, _)| {
|
|
|
|
debug_assert!(self.values[output_stock].unwrap_or(0.0) < 1000000.0, "{:?}", self.values[output_stock]);
|
|
|
|
debug_assert!(self.yields[labor] < 1000000.0, "{}", self.yields[labor]);
|
|
|
|
self.values[output_stock].unwrap_or(0.0) * self.yields[labor]
|
|
|
|
});
|
|
|
|
let labor_ratio_sum = labor_ratios.iter().map(|(_, r)| *r).sum::<f32>().max(0.01);
|
|
|
|
assert!(labor_ratio_sum > 0.0);
|
|
|
|
production.iter().for_each(|(labor, _)| {
|
|
|
|
debug_assert!(!labor_ratios[labor].is_nan() && !labor_ratios[labor].is_infinite(), "{:?}, {}", labor, labor_ratios[labor]);
|
|
|
|
debug_assert!(!labor_ratio_sum.is_nan() && !labor_ratio_sum.is_infinite(), "{:?}, {}", labor, labor_ratio_sum);
|
|
|
|
let smooth = 0.5;
|
|
|
|
self.labors[labor] = smooth * self.labors[labor] + (1.0 - smooth) * (labor_ratios[labor].max(labor_ratio_sum / 1000.0) / labor_ratio_sum);
|
|
|
|
});
|
2020-03-28 18:16:19 +00:00
|
|
|
|
2020-03-30 16:46:44 +00:00
|
|
|
// Production
|
|
|
|
let stocks_before = self.stocks.clone();
|
|
|
|
for (labor, orders) in orders.iter() {
|
|
|
|
let scale = if let Some(labor) = labor { self.labors[*labor] } else { 1.0 } * population;
|
|
|
|
|
|
|
|
// For each order, we try to find the minimum satisfaction rate - this limits how much
|
|
|
|
// we can produce! For example, if we need 0.25 fish and 0.75 oats to make 1 unit of
|
|
|
|
// food, but only 0.5 units of oats are available then we only need to consume 2/3rds
|
|
|
|
// of other ingredients and leave the rest in stock
|
|
|
|
let min_satisfaction = orders
|
|
|
|
.iter()
|
|
|
|
.map(|(stock, amount)| {
|
|
|
|
// What quantity is this order requesting?
|
|
|
|
let quantity = *amount * scale;
|
|
|
|
// What proportion of this order is the economy able to satisfy?
|
|
|
|
let satisfaction = (stocks_before[*stock] / demand[*stock]).min(1.0);
|
|
|
|
satisfaction
|
|
|
|
})
|
|
|
|
.min_by(|a, b| a.partial_cmp(b).unwrap())
|
|
|
|
.unwrap_or_else(|| panic!("Industry {:?} requires at least one input order", labor));
|
|
|
|
|
|
|
|
for (stock, amount) in orders {
|
|
|
|
// What quantity is this order requesting?
|
|
|
|
let quantity = *amount * scale;
|
|
|
|
// What amount gets actually used in production?
|
|
|
|
let used = quantity * min_satisfaction;
|
|
|
|
|
|
|
|
// Deplete stocks accordingly
|
2020-03-30 22:45:41 +00:00
|
|
|
self.stocks[*stock] = (self.stocks[*stock] - used).max(0.0);
|
2020-03-28 18:16:19 +00:00
|
|
|
}
|
2020-03-27 23:06:23 +00:00
|
|
|
|
2020-03-30 16:46:44 +00:00
|
|
|
// Industries produce things
|
|
|
|
if let Some(labor) = labor {
|
|
|
|
let (stock, rate) = production[*labor];
|
|
|
|
let yield_per_worker = min_satisfaction * rate;
|
|
|
|
self.yields[*labor] = yield_per_worker;
|
2020-03-30 22:45:41 +00:00
|
|
|
let workers = self.labors[*labor] * population;
|
|
|
|
self.stocks[stock] += yield_per_worker * workers.powf(1.1);
|
2020-03-28 18:16:19 +00:00
|
|
|
}
|
2020-03-30 16:46:44 +00:00
|
|
|
}
|
2020-03-27 23:06:23 +00:00
|
|
|
|
2020-03-30 22:45:41 +00:00
|
|
|
// Denature stocks
|
|
|
|
self.stocks.iter_mut().for_each(|(_, v)| *v *= 0.9);
|
|
|
|
|
2020-03-30 16:46:44 +00:00
|
|
|
// Births/deaths
|
|
|
|
const NATURAL_BIRTH_RATE: f32 = 0.15;
|
|
|
|
const DEATH_RATE: f32 = 0.05;
|
2020-03-30 22:45:41 +00:00
|
|
|
debug_assert!(!self.surplus[FOOD].is_nan());
|
|
|
|
debug_assert!(!self.surplus[FOOD].is_infinite());
|
|
|
|
let birth_rate = if self.surplus[FOOD] > 0.0 { NATURAL_BIRTH_RATE } else { 0.0 };
|
2020-03-30 16:46:44 +00:00
|
|
|
self.population += years * self.population * (birth_rate - DEATH_RATE);
|
2020-03-28 18:16:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-29 19:48:51 +00:00
|
|
|
type Occupation = &'static str;
|
|
|
|
const FARMER: Occupation = "farmer";
|
|
|
|
const LUMBERJACK: Occupation = "lumberjack";
|
|
|
|
const MINER: Occupation = "miner";
|
|
|
|
const FISHER: Occupation = "fisher";
|
2020-03-30 16:46:44 +00:00
|
|
|
const HUNTER: Occupation = "hunter";
|
|
|
|
const COOK: Occupation = "cook";
|
2020-03-29 19:48:51 +00:00
|
|
|
|
2020-03-28 18:16:19 +00:00
|
|
|
type Stock = &'static str;
|
2020-03-30 16:46:44 +00:00
|
|
|
const WHEAT: Stock = "wheat";
|
|
|
|
const FLOUR: Stock = "flour";
|
|
|
|
const MEAT: Stock = "meat";
|
|
|
|
const FISH: Stock = "fish";
|
|
|
|
const GAME: Stock = "game";
|
2020-03-28 18:16:19 +00:00
|
|
|
const FOOD: Stock = "food";
|
2020-03-30 16:46:44 +00:00
|
|
|
const LOGS: Stock = "logs";
|
2020-03-28 18:16:19 +00:00
|
|
|
const WOOD: Stock = "wood";
|
|
|
|
const ROCK: Stock = "rock";
|
2020-03-30 16:46:44 +00:00
|
|
|
const STONE: Stock = "stone";
|
2020-03-30 22:45:41 +00:00
|
|
|
const TRADE_STOCKS: [Stock; 5] = [
|
|
|
|
FLOUR,
|
|
|
|
MEAT,
|
|
|
|
FOOD,
|
|
|
|
WOOD,
|
|
|
|
STONE,
|
|
|
|
];
|
2020-03-28 18:16:19 +00:00
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
struct TradeState {
|
|
|
|
buy_belief: econ::Belief,
|
|
|
|
sell_belief: econ::Belief,
|
|
|
|
}
|
2020-03-27 23:06:23 +00:00
|
|
|
|
2020-03-28 18:16:19 +00:00
|
|
|
impl Default for TradeState {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
buy_belief: econ::Belief {
|
|
|
|
price: 1.0,
|
|
|
|
confidence: 0.25,
|
|
|
|
},
|
|
|
|
sell_belief: econ::Belief {
|
|
|
|
price: 1.0,
|
|
|
|
confidence: 0.25,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-29 19:48:51 +00:00
|
|
|
pub type Stocks<T> = MapVec<Stock, T>;
|
|
|
|
|
2020-03-28 18:16:19 +00:00
|
|
|
#[derive(Default, Clone, Debug)]
|
2020-03-29 19:48:51 +00:00
|
|
|
pub struct MapVec<K, T> {
|
2020-03-29 20:46:19 +00:00
|
|
|
entries: HashMap<K, T>,
|
2020-03-30 16:46:44 +00:00
|
|
|
default: T,
|
2020-03-28 18:16:19 +00:00
|
|
|
}
|
|
|
|
|
2020-03-29 19:48:51 +00:00
|
|
|
impl<K: Copy + Eq + Hash, T: Default + Clone> MapVec<K, T> {
|
|
|
|
pub fn from_list<'a>(i: impl IntoIterator<Item=&'a (K, T)>) -> Self
|
|
|
|
where K: 'a, T: 'a
|
2020-03-28 18:16:19 +00:00
|
|
|
{
|
|
|
|
Self {
|
2020-03-29 20:46:19 +00:00
|
|
|
entries: i.into_iter().cloned().collect(),
|
2020-03-30 16:46:44 +00:00
|
|
|
default: T::default(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn from_default(default: T) -> Self {
|
|
|
|
Self {
|
|
|
|
entries: HashMap::default(),
|
|
|
|
default,
|
2020-03-28 18:16:19 +00:00
|
|
|
}
|
2020-03-27 23:06:23 +00:00
|
|
|
}
|
|
|
|
|
2020-03-29 20:46:19 +00:00
|
|
|
pub fn get_mut(&mut self, entry: K) -> &mut T {
|
2020-03-30 16:46:44 +00:00
|
|
|
let default = &self.default;
|
2020-03-28 18:16:19 +00:00
|
|
|
self
|
2020-03-29 20:46:19 +00:00
|
|
|
.entries
|
|
|
|
.entry(entry)
|
2020-03-30 16:46:44 +00:00
|
|
|
.or_insert_with(|| default.clone())
|
2020-03-28 18:16:19 +00:00
|
|
|
}
|
|
|
|
|
2020-03-29 20:46:19 +00:00
|
|
|
pub fn get(&self, entry: K) -> &T {
|
2020-03-30 16:46:44 +00:00
|
|
|
self.entries.get(&entry).unwrap_or(&self.default)
|
2020-03-28 18:16:19 +00:00
|
|
|
}
|
|
|
|
|
2020-03-30 16:46:44 +00:00
|
|
|
pub fn map<U: Default>(mut self, mut f: impl FnMut(K, T) -> U) -> MapVec<K, U> {
|
|
|
|
MapVec {
|
|
|
|
entries: self.entries.into_iter().map(|(s, v)| (s.clone(), f(s, v))).collect(),
|
|
|
|
default: U::default(),
|
|
|
|
}
|
2020-03-28 18:16:19 +00:00
|
|
|
}
|
|
|
|
|
2020-03-29 19:48:51 +00:00
|
|
|
pub fn iter(&self) -> impl Iterator<Item=(K, &T)> + '_ {
|
2020-03-29 20:46:19 +00:00
|
|
|
self.entries.iter().map(|(s, v)| (*s, v))
|
2020-03-28 18:16:19 +00:00
|
|
|
}
|
|
|
|
|
2020-03-29 19:48:51 +00:00
|
|
|
pub fn iter_mut(&mut self) -> impl Iterator<Item=(K, &mut T)> + '_ {
|
2020-03-29 20:46:19 +00:00
|
|
|
self.entries.iter_mut().map(|(s, v)| (*s, v))
|
2020-03-27 23:06:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-29 19:48:51 +00:00
|
|
|
impl<K: Copy + Eq + Hash, T: Default + Clone> std::ops::Index<K> for MapVec<K, T> {
|
2020-03-28 18:16:19 +00:00
|
|
|
type Output = T;
|
2020-03-29 20:46:19 +00:00
|
|
|
fn index(&self, entry: K) -> &Self::Output { self.get(entry) }
|
2020-03-27 23:06:23 +00:00
|
|
|
}
|
2020-03-28 18:16:19 +00:00
|
|
|
|
2020-03-29 19:48:51 +00:00
|
|
|
impl<K: Copy + Eq + Hash, T: Default + Clone> std::ops::IndexMut<K> for MapVec<K, T> {
|
2020-03-29 20:46:19 +00:00
|
|
|
fn index_mut(&mut self, entry: K) -> &mut Self::Output { self.get_mut(entry) }
|
2020-03-28 18:16:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|