|
@@ -1,6 +1,8 @@
|
|
|
use std::time::{SystemTime};
|
|
use std::time::{SystemTime};
|
|
|
use rand::Rng;
|
|
use rand::Rng;
|
|
|
-
|
|
|
|
|
|
|
+use std::fs::File;
|
|
|
|
|
+use std::path::Path;
|
|
|
|
|
+use std::fs;
|
|
|
|
|
|
|
|
#[macro_use]
|
|
#[macro_use]
|
|
|
extern crate serde_derive;
|
|
extern crate serde_derive;
|
|
@@ -13,26 +15,36 @@ extern crate serde_json;
|
|
|
/*
|
|
/*
|
|
|
UNITS
|
|
UNITS
|
|
|
mass = kilogram
|
|
mass = kilogram
|
|
|
- time = sec
|
|
|
|
|
|
|
+ time = 1 day
|
|
|
distance = meter
|
|
distance = meter
|
|
|
- speed = meter / tick (s)
|
|
|
|
|
|
|
+ speed = meter / day
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
// Time constants
|
|
// Time constants
|
|
|
-const TICK: i32 = 1;
|
|
|
|
|
-const SECOND: i32 = TICK;
|
|
|
|
|
-const MINUTE: i32 = SECOND*60;
|
|
|
|
|
-const HOUR: i32 = MINUTE*60;
|
|
|
|
|
-const DAY: i32 = HOUR*24;
|
|
|
|
|
-const MONTH: i32 = DAY*30;
|
|
|
|
|
-const YEAR: i32 = MONTH*12;
|
|
|
|
|
|
|
+const SECOND: f32 = MINUTE/60.0;
|
|
|
|
|
+const MINUTE: f32 = HOUR/60.0;
|
|
|
|
|
+const HOUR: f32 = DAY/24.0;
|
|
|
|
|
+const DAY: f32 = 1.0;
|
|
|
|
|
+const MONTH: f32 = DAY*30.0;
|
|
|
|
|
+const YEAR: f32 = MONTH*12.0;
|
|
|
// weight constants
|
|
// weight constants
|
|
|
const KG: f32 = 1.0;
|
|
const KG: f32 = 1.0;
|
|
|
const TON: f32 = KG*1000.0;
|
|
const TON: f32 = KG*1000.0;
|
|
|
|
|
|
|
|
|
|
|
|
|
-// Diet types a lifeform could have
|
|
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
|
+struct Config {
|
|
|
|
|
+ biome: Biome,
|
|
|
|
|
+ lifeforms: Vec<Lifeform>,
|
|
|
|
|
+ simtime: f32,
|
|
|
|
|
+ simsteps: f32
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+// Diet types a lifeform could have
|
|
|
|
|
+#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
|
enum Diets {
|
|
enum Diets {
|
|
|
Carnivore,
|
|
Carnivore,
|
|
|
Herbivore,
|
|
Herbivore,
|
|
@@ -41,33 +53,22 @@ enum Diets {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// The types of lifeforms that may exist
|
|
// The types of lifeforms that may exist
|
|
|
-#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
|
|
|
+#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
|
enum LifeformKind {
|
|
enum LifeformKind {
|
|
|
Fauna,
|
|
Fauna,
|
|
|
Flora,
|
|
Flora,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
|
-struct Biome {
|
|
|
|
|
- water: f32,
|
|
|
|
|
- biomass: f32,
|
|
|
|
|
- lifeforms: Vec<Lifeform>,
|
|
|
|
|
- bounds: Vec<f32> //left, top, right, bottom boundaries
|
|
|
|
|
|
|
+#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
|
|
|
+struct Rect {
|
|
|
|
|
+ x: f32,
|
|
|
|
|
+ y: f32,
|
|
|
|
|
+ width: f32,
|
|
|
|
|
+ height: f32
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- fn move_random(lf: &mut Lifeform, bounds: &Vec<f32>) {
|
|
|
|
|
- let mut rng = rand::thread_rng();
|
|
|
|
|
- let rx = rng.gen_range::<f32>(-1.0, 1.0);
|
|
|
|
|
- let rz = rng.gen_range::<f32>(-1.0, 1.0);
|
|
|
|
|
- lf.position[0] += rx*lf.speed;
|
|
|
|
|
- lf.position[2] += rz*lf.speed;
|
|
|
|
|
|
|
|
|
|
- if lf.position[0] > bounds[2] {lf.position[0] = bounds[2]}; // x > right bound
|
|
|
|
|
- if lf.position[2] > bounds[1] {lf.position[2] = bounds[1]}; // z > upper bound
|
|
|
|
|
- if lf.position[0] < bounds[0] {lf.position[0] = bounds[0]}; // x < left bound
|
|
|
|
|
- if lf.position[2] < bounds[3] {lf.position[2] = bounds[3]}; // z < lower bound
|
|
|
|
|
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
/*
|
|
/*
|
|
|
Biome - a container for lifeforms.
|
|
Biome - a container for lifeforms.
|
|
@@ -75,51 +76,62 @@ Ideally, I wanted to have next to no logic here, but I realized my lifeforms nee
|
|
|
for example to re-deposit their weight back on death, to query for food, etc. I'd like to pass this to a
|
|
for example to re-deposit their weight back on death, to query for food, etc. I'd like to pass this to a
|
|
|
lifeform so it can access it and modify it.
|
|
lifeform so it can access it and modify it.
|
|
|
*/
|
|
*/
|
|
|
|
|
+#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
|
|
|
+struct Biome {
|
|
|
|
|
+ water: f32,
|
|
|
|
|
+ biomass: f32,
|
|
|
|
|
+ lifeforms: Vec<Lifeform>,
|
|
|
|
|
+ rect: Rect
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
impl Biome {
|
|
impl Biome {
|
|
|
- fn tick(&mut self){
|
|
|
|
|
|
|
+ fn tick(&mut self, timescale: f32){
|
|
|
|
|
|
|
|
for mut lifeform in &mut self.lifeforms {
|
|
for mut lifeform in &mut self.lifeforms {
|
|
|
if lifeform.alive {
|
|
if lifeform.alive {
|
|
|
- self.water -= lifeform.water_intake;
|
|
|
|
|
- lifeform.mass += lifeform.nutrient_intake;
|
|
|
|
|
- lifeform.age += 1;
|
|
|
|
|
|
|
+ if self.water > 0.0 {
|
|
|
|
|
+ self.water -= lifeform.water_intake * timescale as f32;
|
|
|
|
|
+ }
|
|
|
|
|
+ lifeform.mass += lifeform.nutrient_intake * timescale as f32;
|
|
|
|
|
+ lifeform.age += 1.0 * timescale;
|
|
|
if lifeform.age > lifeform.maxage {
|
|
if lifeform.age > lifeform.maxage {
|
|
|
lifeform.alive = false;
|
|
lifeform.alive = false;
|
|
|
- println!("Died: {:?}", lifeform);
|
|
|
|
|
|
|
+ //println!("Died: {:?}", lifeform);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
- move_random(&mut lifeform, &self.bounds);
|
|
|
|
|
|
|
+ move_random(&mut lifeform, &self.rect, timescale);
|
|
|
} else {
|
|
} else {
|
|
|
|
|
+ // fuck, we're dead
|
|
|
self.biomass += lifeform.mass;
|
|
self.biomass += lifeform.mass;
|
|
|
lifeform.mass = 0.0;
|
|
lifeform.mass = 0.0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- //lifeform.tick(self);
|
|
|
|
|
- /*
|
|
|
|
|
- if i could do this, i could handle most of the logic in Lifeform which I
|
|
|
|
|
- would prefer, but "cannot borrow `*self` as mutable more than once at a time"
|
|
|
|
|
- */
|
|
|
|
|
-
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
|
|
|
+#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
|
struct Lifeform {
|
|
struct Lifeform {
|
|
|
name: String, //just for fun
|
|
name: String, //just for fun
|
|
|
mass: f32, //how much it weights
|
|
mass: f32, //how much it weights
|
|
|
- age: i32, //age in ticks
|
|
|
|
|
- maxage: i32, //maximum age
|
|
|
|
|
|
|
+ mass_starve: f32, //at this mass, starvation begins
|
|
|
|
|
+ digestive_capacity: f32,//a food "buffer"; stomach or tree trunk
|
|
|
|
|
+ digestive_content: f32, //how much the digestive buffer can store
|
|
|
|
|
+ age: f32, //age in ticks
|
|
|
|
|
+ maxage: f32, //maximum age
|
|
|
water_intake: f32, //how much water it consumes per tick
|
|
water_intake: f32, //how much water it consumes per tick
|
|
|
nutrient_intake: f32, //how much food it consumes per tick
|
|
nutrient_intake: f32, //how much food it consumes per tick
|
|
|
diet: Diets, //the type of stuff it eats
|
|
diet: Diets, //the type of stuff it eats
|
|
|
kind: LifeformKind, //the kind of lifeform it is
|
|
kind: LifeformKind, //the kind of lifeform it is
|
|
|
alive: bool, //whether it lives or not
|
|
alive: bool, //whether it lives or not
|
|
|
position: Vec<f32>, //where it hangs out
|
|
position: Vec<f32>, //where it hangs out
|
|
|
- speed: f32, //how fast it can move (or not)
|
|
|
|
|
|
|
+ speed: f32, //how fast it can move (or not),
|
|
|
|
|
+ ocurrence_per_sq_km: f32//distribution
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -131,17 +143,22 @@ impl Lifeform {
|
|
|
i would like to be able to pass the biome to the lifeform as a reference to that
|
|
i would like to be able to pass the biome to the lifeform as a reference to that
|
|
|
it could interact with it.
|
|
it could interact with it.
|
|
|
*/
|
|
*/
|
|
|
- fn tick(&mut self, biome: &mut Biome){
|
|
|
|
|
|
|
+ // fn tick(&mut self, biome: &mut Biome){
|
|
|
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
impl Default for Lifeform {
|
|
impl Default for Lifeform {
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
fn default() -> Lifeform {
|
|
fn default() -> Lifeform {
|
|
|
Lifeform {
|
|
Lifeform {
|
|
|
name: "Generic Plant".to_string(),
|
|
name: "Generic Plant".to_string(),
|
|
|
mass: 1.0,
|
|
mass: 1.0,
|
|
|
- age: 0,
|
|
|
|
|
|
|
+ mass_starve: 0.0,
|
|
|
|
|
+ digestive_capacity: 0.0,
|
|
|
|
|
+ digestive_content: 0.0,
|
|
|
|
|
+ age: 0.0,
|
|
|
water_intake: 1.0/DAY as f32,
|
|
water_intake: 1.0/DAY as f32,
|
|
|
nutrient_intake: 1.0/DAY as f32,
|
|
nutrient_intake: 1.0/DAY as f32,
|
|
|
diet: Diets::Herbivore,
|
|
diet: Diets::Herbivore,
|
|
@@ -150,72 +167,121 @@ impl Default for Lifeform {
|
|
|
alive: true,
|
|
alive: true,
|
|
|
position: vec![0.0, 0.0, 0.0],
|
|
position: vec![0.0, 0.0, 0.0],
|
|
|
speed: 0.0,
|
|
speed: 0.0,
|
|
|
|
|
+ ocurrence_per_sq_km: 1.0
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-fn main() {
|
|
|
|
|
- let start = SystemTime::now();
|
|
|
|
|
- println!("{:?}", start);
|
|
|
|
|
|
|
|
|
|
- let mut biome = Biome {water: 1000.0, lifeforms: Vec::new(), biomass: TON, bounds: vec![0.0, 100.0, 100.0, 0.0]};
|
|
|
|
|
|
|
+fn move_random(lf: &mut Lifeform, rect: &Rect, timescale: f32) {
|
|
|
|
|
+ let mut rng = rand::thread_rng();
|
|
|
|
|
+ let rx = rng.gen_range::<f32>(-1.0, 1.0);
|
|
|
|
|
+ let rz = rng.gen_range::<f32>(-1.0, 1.0);
|
|
|
|
|
+ lf.position[0] += (rx * lf.speed) * timescale as f32;
|
|
|
|
|
+ lf.position[2] += (rz * lf.speed) * timescale as f32;
|
|
|
|
|
+ // where is clamp
|
|
|
|
|
+ if lf.position[0] > rect.width+rect.x {lf.position[0] = rect.width+rect.x}; // x > right bound
|
|
|
|
|
+ if lf.position[2] > rect.height+rect.y {lf.position[2] = rect.height+rect.y}; // z > upper bound
|
|
|
|
|
+ if lf.position[0] < rect.x {lf.position[0] = rect.x}; // x < left bound
|
|
|
|
|
+ if lf.position[2] < rect.y {lf.position[2] = rect.y}; // z < lower bound
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
|
|
|
|
|
+fn sim() {
|
|
|
|
|
|
|
|
// add some trees
|
|
// add some trees
|
|
|
- for _i in 1..400 {
|
|
|
|
|
- let mut rng = rand::thread_rng();
|
|
|
|
|
- let age = rng.gen_range::<i32>(1*MONTH, 10*YEAR);
|
|
|
|
|
- let maxage = rng.gen_range::<i32>(5*YEAR, 50*YEAR);
|
|
|
|
|
- let mass = rng.gen_range::<f32>(50.0, 6.0*TON);
|
|
|
|
|
- let water_intake = rng.gen_range::<f32>(50.0, 150.0)/DAY as f32;
|
|
|
|
|
- let mut tree = Lifeform {
|
|
|
|
|
- name: "Tree".to_string(),
|
|
|
|
|
- age: age,
|
|
|
|
|
- maxage: maxage,
|
|
|
|
|
- mass: mass,
|
|
|
|
|
- water_intake: water_intake,
|
|
|
|
|
- diet: Diets::Photovore,
|
|
|
|
|
- alive: true,
|
|
|
|
|
- kind: LifeformKind::Flora,
|
|
|
|
|
- ..Default::default()
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ let mut rng_tree = rand::thread_rng();
|
|
|
|
|
+ let tree = Lifeform {
|
|
|
|
|
+ name: "Tree".to_string(),
|
|
|
|
|
+ age: rng_tree.gen_range::<f32>(1.0*MONTH, 10.0*YEAR),
|
|
|
|
|
+ maxage: rng_tree.gen_range::<f32>(5.0*YEAR, 50.0*YEAR),
|
|
|
|
|
+ mass: rng_tree.gen_range::<f32>(50.0, 6.0*TON),
|
|
|
|
|
+ water_intake: rng_tree.gen_range::<f32>(50.0, 150.0)/DAY,
|
|
|
|
|
+ diet: Diets::Photovore,
|
|
|
|
|
+ alive: true,
|
|
|
|
|
+ kind: LifeformKind::Flora,
|
|
|
|
|
+ ..Default::default()
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- biome.lifeforms.push(tree);
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
// add deer
|
|
// add deer
|
|
|
- for _i in 1..10 {
|
|
|
|
|
- let mut rng = rand::thread_rng();
|
|
|
|
|
- let age = rng.gen_range::<i32>(1*MONTH, 5*YEAR);
|
|
|
|
|
- let maxage = rng.gen_range::<i32>(1*YEAR, 5*YEAR);
|
|
|
|
|
- let mass = rng.gen_range::<f32>(4.0, 17.0);
|
|
|
|
|
- let water_intake = rng.gen_range::<f32>(0.5, 1.0)/DAY as f32;
|
|
|
|
|
- let speed = rng.gen_range::<f32>(0.0/HOUR as f32, 10000.0/HOUR as f32) as f32;
|
|
|
|
|
|
|
+ let mut rng_deer = rand::thread_rng();
|
|
|
|
|
+ let deer = Lifeform {
|
|
|
|
|
+ name: "Deer".to_string(),
|
|
|
|
|
+ kind: LifeformKind::Fauna,
|
|
|
|
|
+ age: rng_deer.gen_range::<f32>(1.0*MONTH, 5.0*YEAR),
|
|
|
|
|
+ maxage: rng_deer.gen_range::<f32>(2.0*YEAR, 6.0*YEAR),
|
|
|
|
|
+ mass: rng_deer.gen_range::<f32>(4.0, 17.0),
|
|
|
|
|
+ water_intake: rng_deer.gen_range::<f32>(0.5, 1.0)/DAY,
|
|
|
|
|
+ speed: rng_deer.gen_range::<f32>(0.0, 10000.0/HOUR),
|
|
|
|
|
+ digestive_capacity: 2.0,
|
|
|
|
|
+ mass_starve: 5.0,
|
|
|
|
|
+ ocurrence_per_sq_km: 15.0,
|
|
|
|
|
+ ..Default::default()};
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ let gen_config = false;
|
|
|
|
|
+
|
|
|
|
|
+ if gen_config {
|
|
|
|
|
+ let config_rect = Rect {x: 0.0, y: 0.0, width: 100.0, height: 100.0};
|
|
|
|
|
+
|
|
|
|
|
+ let config_biome = Biome {water: 1000.0, lifeforms: Vec::new(), biomass: TON, rect: config_rect};
|
|
|
|
|
+ let mut config_lifeforms = Vec::new();
|
|
|
|
|
+ config_lifeforms.push(deer);
|
|
|
|
|
+ config_lifeforms.push(tree);
|
|
|
|
|
+
|
|
|
|
|
+ let config = Config {biome: config_biome, lifeforms: config_lifeforms, simtime: 1000.0, simsteps: 1.0};
|
|
|
|
|
+ let serialized_config = serde_json::to_string_pretty(&config).unwrap();
|
|
|
|
|
|
|
|
- let mut deer = Lifeform {
|
|
|
|
|
- name: "Deer".to_string(),
|
|
|
|
|
- kind: LifeformKind::Fauna,
|
|
|
|
|
- age: age,
|
|
|
|
|
- maxage: maxage,
|
|
|
|
|
- mass: mass,
|
|
|
|
|
- water_intake: water_intake,
|
|
|
|
|
- speed: speed,
|
|
|
|
|
- ..Default::default()};
|
|
|
|
|
- biome.lifeforms.push(deer);
|
|
|
|
|
|
|
+ fs::write("config.json", serialized_config).expect("Unable to write config file");
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ //biome.lifeforms.push(deer);
|
|
|
|
|
+ //biome.lifeforms.push(tree);
|
|
|
|
|
+
|
|
|
|
|
+ // let deserialized: Plant = serde_json::from_str(&serialized).unwrap();
|
|
|
|
|
+ //println!("deserialized = {:?}", deserialized);
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ // Load config file
|
|
|
|
|
+ let json_config_path = Path::new("config.json");
|
|
|
|
|
+ let config_file = File::open(json_config_path).expect("config file not found");
|
|
|
|
|
+ let deserialized_config: Config = serde_json::from_reader(config_file).expect("error while reading json config");
|
|
|
|
|
+ // println!("{:?}", deserialized_config);
|
|
|
|
|
+
|
|
|
|
|
|
|
|
|
|
+ let mut loaded_biome = deserialized_config.biome;
|
|
|
|
|
|
|
|
|
|
+ for lf in deserialized_config.lifeforms {
|
|
|
|
|
+
|
|
|
|
|
+ let sq_km = (loaded_biome.rect.width*loaded_biome.rect.width)/1000000.0;
|
|
|
|
|
+ let num_instances = sq_km * lf.ocurrence_per_sq_km;
|
|
|
|
|
+ println!("\nInstantiating from {:?} {:?} times", lf.name, num_instances);
|
|
|
|
|
+ for _i in 0..num_instances as i32 {
|
|
|
|
|
+ let mut instance = lf.clone();
|
|
|
|
|
+ loaded_biome.lifeforms.push(instance);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
|
|
|
- let simtime: i32 = 5*HOUR;
|
|
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- for _i in 1..simtime {
|
|
|
|
|
- biome.tick();
|
|
|
|
|
|
|
+ //biome.lifeforms.push(tree);
|
|
|
|
|
+ println!("running {:?} days for {:?} lifeforms", deserialized_config.simtime * deserialized_config.simsteps, loaded_biome.lifeforms.len());
|
|
|
|
|
+ for _i in 1..deserialized_config.simtime as i32 {
|
|
|
|
|
+ loaded_biome.tick(deserialized_config.simsteps);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- let serialized = serde_json::to_string(&biome).unwrap();
|
|
|
|
|
- println!("serialized = {}", serialized);
|
|
|
|
|
|
|
+ let serialized = serde_json::to_string(&loaded_biome).unwrap();
|
|
|
|
|
+ //println!("serialized = {}", serialized);
|
|
|
|
|
+ fs::write("sim.json", serialized).expect("Unable to write file");
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- //let deserialized: Plant = serde_json::from_str(&serialized).unwrap();
|
|
|
|
|
- //println!("deserialized = {:?}", deserialized);
|
|
|
|
|
|
|
+
|
|
|
|
|
+fn main() {
|
|
|
|
|
+ //let start = SystemTime::now();
|
|
|
|
|
+ println!("Starting simulation");
|
|
|
|
|
+ sim();
|
|
|
}
|
|
}
|