Began work on basic economic simulation

This commit is contained in:
Joshua Barretto 2020-03-27 23:06:23 +00:00
parent 348003fc1a
commit 46190aa634
8 changed files with 223 additions and 55 deletions

View File

@ -40,6 +40,8 @@ void main() {
f_pos_norm = v_pos_norm;
f_pos.z -= 0.2;
gl_Position =
all_mat *
vec4(f_pos, 1);

View File

@ -45,6 +45,8 @@ impl<T> Store<T> {
pub fn iter(&self) -> impl Iterator<Item = &T> { self.items.iter() }
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> { self.items.iter_mut() }
pub fn iter_ids(&self) -> impl Iterator<Item = (Id<T>, &T)> {
self
.items

View File

@ -18,14 +18,14 @@ fn main() {
let map_file =
// "map_1575990726223.bin";
// "map_1575987666972.bin";
"map_1576046079066.bin";
let mut _map_file = PathBuf::from("./maps");
_map_file.push(map_file);
"map_1585335358316.bin";
let mut map_path = PathBuf::from("./maps");
map_path.push(map_file);
let world = World::generate(5284, WorldOpts {
seed_elements: false,
// world_file: sim::FileOpts::Load(_map_file),
world_file: sim::FileOpts::Save,
world_file: sim::FileOpts::Load(map_path),
//world_file: sim::FileOpts::Save,
..WorldOpts::default()
});
@ -150,12 +150,16 @@ fn main() {
}
if win.get_mouse_down(minifb::MouseButton::Left) {
if let Some((mx, my)) = win.get_mouse_pos(minifb::MouseMode::Clamp) {
let pos = (Vec2::<f64>::from(focus) + (Vec2::new(mx as f64, my as f64) * scale))
let chunk_pos = (Vec2::<f64>::from(focus) + (Vec2::new(mx as f64, my as f64) * scale))
.map(|e| e as i32);
println!(
"Chunk position: {:?}",
pos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e * f as i32)
);
let block_pos = chunk_pos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e * f as i32);
println!("Block: ({}, {}), Chunk: ({}, {})", block_pos.x, block_pos.y, chunk_pos.x, chunk_pos.y);
if let Some(chunk) = sampler.get(chunk_pos) {
//println!("Chunk info: {:#?}", chunk);
if let Some(place) = &chunk.place {
println!("Place {} info: {:#?}", place.id(), world.civs().place(*place));
}
}
}
}
let is_camera = win.is_key_down(minifb::Key::C);

View File

@ -9,7 +9,7 @@ use common::{
path::Path,
astar::Astar,
};
use crate::sim::WorldSim;
use crate::sim::{WorldSim, SimChunk};
const CARDINALS: [Vec2<i32>; 4] = [
Vec2::new(1, 0),
@ -39,8 +39,11 @@ const INITIAL_CIV_COUNT: usize = 20;
pub struct Civs {
civs: Store<Civ>,
places: Store<Place>,
tracks: Store<Track>,
track_map: HashMap<Id<Place>, HashMap<Id<Place>, Id<Track>>>,
track_map: HashMap<Id<Site>, HashMap<Id<Site>, Id<Track>>>,
sites: Store<Site>,
}
struct GenCtx<'a, R: Rng> {
@ -55,13 +58,18 @@ impl Civs {
let mut ctx = GenCtx { sim, rng: &mut rng };
for _ in 0..INITIAL_CIV_COUNT {
if let Some(civ) = this.birth_civ(&mut ctx) {
println!("Initial civilisation: {:#?}", this.civs.get(civ));
} else {
println!("Failed to find starting site");
println!("Creating civilisation...");
if let None = this.birth_civ(&mut ctx) {
println!("Failed to find starting site for civilisation.");
}
}
// Tick
const SIM_YEARS: usize = 100;
for _ in 0..SIM_YEARS {
this.tick(1.0);
}
// Temporary!
for track in this.tracks.iter() {
for loc in track.path.iter() {
@ -69,11 +77,28 @@ impl Civs {
}
}
this.display_info();
this
}
pub fn place(&self, id: Id<Place>) -> &Place { self.places.get(id) }
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);
println!("{:?}", site);
}
}
/// Return the direct track between two places
fn track_between(&self, a: Id<Place>, b: Id<Place>) -> Option<Id<Track>> {
fn track_between(&self, a: Id<Site>, b: Id<Site>) -> Option<Id<Track>> {
self.track_map
.get(&a)
.and_then(|dests| dests.get(&b))
@ -83,17 +108,19 @@ impl Civs {
.copied()
}
/// 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()
}
/// Find the cheapest route between two places
fn route_between(&self, a: Id<Place>, b: Id<Place>) -> Option<(Path<Id<Place>>, f32)> {
let heuristic = move |p: &Id<Place>| (self.places.get(*p).center.distance_squared(self.places.get(b).center) as f32).sqrt();
let neighbors = |p: &Id<Place>| {
let p = *p;
let to = self.track_map.get(&p).map(|dests| dests.keys()).into_iter().flatten();
let fro = self.track_map.iter().filter(move |(_, dests)| dests.contains_key(&p)).map(|(p, _)| p);
to.chain(fro).filter(|p| **p != a).copied()
};
let transition = |a: &Id<Place>, b: &Id<Place>| self.tracks.get(self.track_between(*a, *b).unwrap()).cost;
let satisfied = |p: &Id<Place>| *p == b;
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;
let mut astar = Astar::new(100, a, heuristic);
astar
.poll(100, heuristic, neighbors, transition, satisfied)
@ -102,14 +129,17 @@ impl Civs {
}
fn birth_civ(&mut self, ctx: &mut GenCtx<impl Rng>) -> Option<Id<Civ>> {
const CIV_BIRTHPLACE_AREA: Range<usize> = 64..256;
let place = attempt(5, || {
let site = attempt(5, || {
let loc = find_site_loc(ctx, None)?;
self.establish_place(ctx, loc, CIV_BIRTHPLACE_AREA)
self.establish_site(ctx, loc, SiteKind::Settlement(Settlement {
stocks: Stocks::default(),
population: 24,
}))
})?;
let civ = self.civs.insert(Civ {
homeland: place,
capital: site,
homeland: self.sites.get(site).place,
});
Some(civ)
@ -144,11 +174,37 @@ impl Civs {
let place = self.places.insert(Place {
center: loc,
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)
}
fn establish_site(&mut self, ctx: &mut GenCtx<impl Rng>, loc: Vec2<i32>, kind: SiteKind) -> Option<Id<Site>> {
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 {
kind,
center: loc,
place: place,
});
// Find neighbors
const MAX_NEIGHBOR_DISTANCE: f32 = 250.0;
let mut nearby = self.places
let mut nearby = self.sites
.iter_ids()
.map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt()))
.filter(|(p, dist)| *dist < MAX_NEIGHBOR_DISTANCE)
@ -157,10 +213,10 @@ impl Civs {
for (nearby, _) in nearby.into_iter().take(ctx.rng.gen_range(3, 5)) {
// Find a novel path
if let Some((path, cost)) = find_path(ctx, loc, self.places.get(nearby).center) {
if let Some((path, cost)) = find_path(ctx, loc, self.sites.get(nearby).center) {
// Find a path using existing paths
if self
.route_between(place, nearby)
.route_between(site, nearby)
// 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()
@ -170,21 +226,25 @@ impl Civs {
path,
});
self.track_map
.entry(place)
.entry(site)
.or_default()
.insert(nearby, track);
}
}
}
// Write place to map
for cell in dead.union(&alive) {
if let Some(chunk) = ctx.sim.get_mut(*cell) {
chunk.place = Some(place);
Some(site)
}
pub fn tick(&mut self, years: f32) {
for site in self.sites.iter_mut() {
match &mut site.kind {
SiteKind::Settlement(s) => {
s.collect_stocks(years, &self.places.get(site.place).nat_res);
s.consume_stocks(years);
},
}
}
Some(place)
}
}
@ -212,7 +272,7 @@ fn walk_in_dir(sim: &WorldSim, a: Vec2<i32>, dir: Vec2<i32>) -> Option<f32> {
{
let a_alt = sim.get(a)?.alt;
let b_alt = sim.get(a + dir)?.alt;
Some(0.5 + (b_alt - a_alt).max(-0.5).abs() / 5.0)
Some((b_alt - a_alt).abs() / 2.5)
} else {
None
}
@ -236,7 +296,8 @@ fn site_in_dir(sim: &WorldSim, a: Vec2<i32>, dir: Vec2<i32>) -> bool {
/// 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) {
!chunk.is_underwater() &&
!chunk.river.is_ocean() &&
!chunk.river.is_lake() &&
sim.get_gradient_approx(loc).map(|grad| grad < 1.0).unwrap_or(false)
} else {
false
@ -272,11 +333,39 @@ fn find_site_loc(ctx: &mut GenCtx<impl Rng>, near: Option<(Vec2<i32>, f32)>) ->
#[derive(Debug)]
pub struct Civ {
capital: Id<Site>,
homeland: Id<Place>,
}
#[derive(Debug)]
pub struct Place {
center: Vec2<i32>,
nat_res: NaturalResources,
}
// Productive capacity per year
#[derive(Default, Debug)]
pub struct NaturalResources {
wood: f32,
stone: f32,
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;
self.stone += chunk.rockiness;
self.river += if chunk.river.is_river() { 1.0 } else { 0.0 };
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 };
}
}
pub struct Track {
@ -285,3 +374,66 @@ pub struct Track {
cost: f32,
path: Path<Vec2<i32>>,
}
#[derive(Debug)]
pub struct Site {
kind: SiteKind,
center: Vec2<i32>,
place: Id<Place>,
}
#[derive(Debug)]
pub enum SiteKind {
Settlement(Settlement),
}
#[derive(Default, Debug)]
pub struct Settlement {
stocks: Stocks,
population: u32,
}
impl Settlement {
pub fn collect_stocks(&mut self, years: f32, nat_res: &NaturalResources) {
// Per labourer, per year
const LUMBER_RATE: f32 = 0.5;
const MINE_RATE: f32 = 0.3;
const FARM_RATE: f32 = 0.4;
// No more that 1.0 in total
let lumberjacks = 0.2 * self.population as f32;
let miners = 0.15 * self.population as f32;
let farmers = 0.4 * self.population as f32;
self.stocks.logs += years * nat_res.wood.min(lumberjacks * LUMBER_RATE);
self.stocks.rocks += years * nat_res.stone.min(miners * MINE_RATE);
self.stocks.food += years * nat_res.farmland.min(farmers * FARM_RATE);
}
pub fn consume_stocks(&mut self, years: f32) {
const EAT_RATE: f32 = 0.15;
// Food required to give birth
const BIRTH_FOOD: f32 = 0.25;
const MAX_ANNUAL_BABIES: f32 = 0.15;
let needed_food = self.population as f32 * EAT_RATE;
let food_surplus = (self.stocks.food - needed_food).max(0.0);
let food_deficit = -(self.stocks.food - needed_food).min(0.0);
self.stocks.food = (self.stocks.food - needed_food).max(0.0);
self.population -= (food_deficit * EAT_RATE).round() as u32;
self.population += (food_surplus / BIRTH_FOOD).round().min(self.population as f32 * MAX_ANNUAL_BABIES) as u32;
}
pub fn happiness(&self) -> f32 {
self.stocks.food / self.population as f32
}
}
#[derive(Default, Debug)]
pub struct Stocks {
logs: f32,
rocks: f32,
food: f32,
}

View File

@ -2,7 +2,7 @@ use crate::{
all::ForestKind,
block::StructureMeta,
sim::{
local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, LocationInfo, RiverKind, SimChunk,
local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, RiverKind, SimChunk,
WorldSim,
},
util::{RandomPerm, Sampler, UnitChooser},
@ -1097,7 +1097,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
temp,
humidity,
spawn_rate,
location: sim_chunk.location.as_ref(),
stone_col,
chunk: sim_chunk,
@ -1129,7 +1128,6 @@ pub struct ColumnSample<'a> {
pub temp: f32,
pub humidity: f32,
pub spawn_rate: f32,
pub location: Option<&'a LocationInfo>,
pub stone_col: Rgb<u8>,
pub chunk: &'a SimChunk,

View File

@ -47,6 +47,8 @@ impl World {
pub fn sim(&self) -> &sim::WorldSim { &self.sim }
pub fn civs(&self) -> &civ::Civs { &self.civs }
pub fn tick(&self, _dt: Duration) {
// TODO
}

View File

@ -1391,6 +1391,7 @@ impl WorldSim {
// Place the locations onto the world
let gen = StructureGen2d::new(self.seed, cell_size as u32, cell_size as u32 / 2);
/*
self.chunks
.par_iter_mut()
.enumerate()
@ -1447,6 +1448,7 @@ impl WorldSim {
}
}
});
*/
// Stage 2 - towns!
let chunk_idx_center = |e: Vec2<i32>| {
@ -1767,6 +1769,7 @@ impl WorldSim {
}
}
#[derive(Debug)]
pub struct SimChunk {
pub chaos: f32,
pub alt: f32,
@ -1782,7 +1785,6 @@ pub struct SimChunk {
pub tree_density: f32,
pub forest_kind: ForestKind,
pub spawn_rate: f32,
pub location: Option<LocationInfo>,
pub river: RiverData,
pub sites: Vec<Site>,
@ -1798,12 +1800,6 @@ pub struct RegionInfo {
pub seed: u32,
}
#[derive(Clone)]
pub struct LocationInfo {
pub loc_idx: usize,
pub near: Vec<RegionInfo>,
}
impl SimChunk {
fn generate(posi: usize, gen_ctx: &GenCtx, gen_cdf: &GenCdf) -> Self {
let pos = uniform_idx_as_vec2(posi);
@ -2023,7 +2019,6 @@ impl SimChunk {
}
},
spawn_rate: 1.0,
location: None,
river,
sites: Vec::new(),
@ -2037,11 +2032,16 @@ impl SimChunk {
pub fn get_base_z(&self) -> f32 { self.alt - self.chaos * 50.0 - 16.0 }
pub fn get_name(&self, world: &WorldSim) -> Option<String> {
// TODO
None
/*
if let Some(loc) = &self.location {
Some(world.locations[loc.loc_idx].name().to_string())
} else {
None
}
*/
}
pub fn get_biome(&self) -> BiomeKind {

View File

@ -11,7 +11,7 @@ use common::{
terrain::Block,
vol::{BaseVol, RectSizedVol, WriteVol},
};
use std::sync::Arc;
use std::{fmt, sync::Arc};
use vek::*;
#[derive(Clone)]
@ -47,3 +47,11 @@ impl Site {
impl From<Settlement> for Site {
fn from(settlement: Settlement) -> Self { Site::Settlement(Arc::new(settlement)) }
}
impl fmt::Debug for Site {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Site::Settlement(_) => write!(f, "Settlement"),
}
}
}