mirror of
https://github.com/osmarks/ewo3.git
synced 2026-08-10 12:28:54 +00:00
remove silly mistakes
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
#![feature(test)]
|
||||
extern crate test;
|
||||
|
||||
pub mod components;
|
||||
pub mod map;
|
||||
pub mod plant;
|
||||
|
||||
+26
-22
@@ -1,6 +1,3 @@
|
||||
#![feature(test)]
|
||||
extern crate test;
|
||||
|
||||
use hecs::{CommandBuffer, Entity, With, World};
|
||||
use futures_util::{stream::TryStreamExt, SinkExt, StreamExt};
|
||||
use lazy_static::lazy_static;
|
||||
@@ -178,6 +175,8 @@ struct GameState {
|
||||
baseline_temperature: Map<f32>,
|
||||
dynamic_soil_nutrients: Map<f32>,
|
||||
dynamic_groundwater: Map<f32>,
|
||||
dynamic_soil_nutrients_back: Map<f32>,
|
||||
dynamic_groundwater_back: Map<f32>,
|
||||
positions: PositionIndex,
|
||||
metrics: GameMetrics,
|
||||
}
|
||||
@@ -275,15 +274,17 @@ fn game_state_from_saved(saved: SavedGame) -> Result<GameState> {
|
||||
clients: Slab::new(),
|
||||
ticks: saved.ticks,
|
||||
rng: fastrand::Rng::with_seed(saved.rng_seed),
|
||||
map: saved.map,
|
||||
baseline_soil_nutrients,
|
||||
baseline_groundwater,
|
||||
baseline_salt,
|
||||
baseline_temperature,
|
||||
dynamic_soil_nutrients: saved.dynamic_soil_nutrients,
|
||||
dynamic_groundwater: saved.dynamic_groundwater,
|
||||
dynamic_groundwater_back: Map::new(saved.map.radius, 0.0),
|
||||
dynamic_soil_nutrients_back: Map::new(saved.map.radius, 0.0),
|
||||
positions,
|
||||
metrics: saved.metrics,
|
||||
map: saved.map,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -291,14 +292,16 @@ fn game_state_from_saved(saved: SavedGame) -> Result<GameState> {
|
||||
fn establishment_probability(pos: Coord, genome: &plant::Genome, state: &GameState) -> f32 {
|
||||
let mut crowding = 0.0;
|
||||
|
||||
for range in 1..4 {
|
||||
for offset in hex_circle(range) {
|
||||
let newpos = pos + offset;
|
||||
if let Some(entity) = state.positions.entities[newpos] {
|
||||
if let Ok(plant) = state.world.get::<&Plant>(entity) {
|
||||
// TODO: this maybe ought to be more discontinuous? also work out scale
|
||||
crowding += (1.0 + plant.current_size.ln_1p()) / (range as f32);
|
||||
}
|
||||
for offset in hex_range(4) {
|
||||
let range = vec_length(offset);
|
||||
if range == 0 {
|
||||
continue;
|
||||
}
|
||||
let newpos = pos + offset;
|
||||
if let Some(entity) = state.positions.entities[newpos] {
|
||||
if let Ok(plant) = state.world.get::<&Plant>(entity) {
|
||||
// TODO: this maybe ought to be more discontinuous? also work out scale
|
||||
crowding += (1.0 + plant.current_size.ln_1p()) / (range as f32);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -319,18 +322,17 @@ async fn game_tick(state: &mut GameState) -> Result<()> {
|
||||
buffer.apply(state);
|
||||
|
||||
if state.ticks % FIELD_DECAY_DELAY == 0 {
|
||||
state.dynamic_soil_nutrients.for_each_mut(|nutrients| *nutrients *= 0.9996);
|
||||
state.dynamic_soil_nutrients.for_each_mut(|nutrients| *nutrients *= 0.9999);
|
||||
radius_1_conv(&state.dynamic_soil_nutrients, &mut state.dynamic_soil_nutrients_back, &[0.9999, 0.9999, 0.9999, 0.9999, 0.9999, 0.9999, 0.9999]);
|
||||
std::mem::swap(&mut state.dynamic_soil_nutrients, &mut state.dynamic_soil_nutrients_back);
|
||||
} else if state.ticks % FIELD_DECAY_DELAY == 1 {
|
||||
radius_1_conv(&state.dynamic_groundwater, &mut state.dynamic_groundwater_back, &[0.999, 0.999, 0.999, 0.999, 0.999, 0.999, 0.999]);
|
||||
std::mem::swap(&mut state.dynamic_groundwater, &mut state.dynamic_groundwater_back);
|
||||
for (pos, water) in state.dynamic_groundwater.iter_mut() {
|
||||
*water *= 0.999;
|
||||
if state.map.water[pos] > 0.0 {
|
||||
*water *= 0.9; // reversion to baseline much faster in active water areas
|
||||
}
|
||||
}
|
||||
} else if state.ticks % FIELD_DECAY_DELAY == 2 {
|
||||
state.dynamic_soil_nutrients = smooth(&state.dynamic_soil_nutrients, 1);
|
||||
} else if state.ticks % FIELD_DECAY_DELAY == 3 {
|
||||
state.dynamic_groundwater = smooth(&state.dynamic_groundwater, 1);
|
||||
}
|
||||
|
||||
// Spawn enemies
|
||||
@@ -438,8 +440,8 @@ async fn game_tick(state: &mut GameState) -> Result<()> {
|
||||
plant.nutrients_consumed += difference * SOIL_NUTRIENT_CONSUMPTION_RATE;
|
||||
plant.total_growth += difference;
|
||||
if plant.current_size > plant.genome.mature_size() {
|
||||
state.dynamic_soil_nutrients[pos] += plant.genome.nutrient_addition_rate() * SOIL_NUTRIENT_FIXATION_RATE;
|
||||
plant.nutrients_added += plant.genome.nutrient_addition_rate() * SOIL_NUTRIENT_FIXATION_RATE;
|
||||
state.dynamic_soil_nutrients[pos] += plant.genome.nutrient_addition_rate() * SOIL_NUTRIENT_FIXATION_RATE * plant.current_size;
|
||||
plant.nutrients_added += plant.genome.nutrient_addition_rate() * SOIL_NUTRIENT_FIXATION_RATE * plant.current_size;
|
||||
} else if plant.current_size <= 0.0 {
|
||||
plant.current_size = 0.1;
|
||||
}
|
||||
@@ -792,7 +794,7 @@ async fn game_tick(state: &mut GameState) -> Result<()> {
|
||||
let mut nearby = vec![];
|
||||
if let Ok(pos) = state.world.get::<&Position>(client.entity) {
|
||||
let pos = pos.head();
|
||||
for offset in hex_circle(VIEW) {
|
||||
for offset in hex_range(VIEW) {
|
||||
let pos = pos + offset;
|
||||
let mut rng = rng_from_hash(pos);
|
||||
|
||||
@@ -921,7 +923,6 @@ async fn main() -> Result<()> {
|
||||
ticks: 0,
|
||||
rng: fastrand::Rng::with_seed(fastrand::u64(..)),
|
||||
positions: PositionIndex::new(world.radius),
|
||||
map: world,
|
||||
baseline_soil_nutrients,
|
||||
baseline_groundwater,
|
||||
baseline_salt,
|
||||
@@ -929,6 +930,9 @@ async fn main() -> Result<()> {
|
||||
dynamic_soil_nutrients,
|
||||
dynamic_groundwater,
|
||||
metrics: GameMetrics::new(),
|
||||
dynamic_groundwater_back: Map::new(world.radius, 0.0),
|
||||
dynamic_soil_nutrients_back: Map::new(world.radius, 0.0),
|
||||
map: world,
|
||||
}))
|
||||
};
|
||||
|
||||
|
||||
+87
-53
@@ -46,7 +46,7 @@ pub fn sample_range(rng: &mut fastrand::Rng, range: i32) -> CoordVec {
|
||||
CoordVec::new(q, r)
|
||||
}
|
||||
|
||||
pub fn hex_circle(range: i32) -> impl Iterator<Item=CoordVec> {
|
||||
pub fn hex_range(range: i32) -> impl Iterator<Item=CoordVec> {
|
||||
(-range..=range).flat_map(move |q| {
|
||||
((-range).max(-q - range)..= range.min(-q+range)).map(move |r| {
|
||||
CoordVec::new(q, r)
|
||||
@@ -54,61 +54,28 @@ pub fn hex_circle(range: i32) -> impl Iterator<Item=CoordVec> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn hex_range(range: i32) -> impl Iterator<Item=(i32, CoordVec)> {
|
||||
(0..=range).flat_map(|x| hex_circle(x).map(move |c| (x, c)))
|
||||
}
|
||||
|
||||
|
||||
pub fn count_hexes(x: i32) -> i32 {
|
||||
x*(x+1)*3+1
|
||||
}
|
||||
|
||||
struct CoordIterator {
|
||||
radius: i32,
|
||||
count: usize,
|
||||
r: i32,
|
||||
q: i32,
|
||||
max: usize
|
||||
}
|
||||
|
||||
struct CoordIterMut<'a, T> {
|
||||
coords: CoordIterator,
|
||||
struct CoordIterMut<'a, T, I: Iterator<Item=Coord>> {
|
||||
coords: I,
|
||||
data: *mut T,
|
||||
radius: i32,
|
||||
side_length: usize,
|
||||
_marker: PhantomData<&'a mut T>
|
||||
}
|
||||
|
||||
impl Iterator for CoordIterator {
|
||||
type Item = Coord;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.count == self.max {
|
||||
return None;
|
||||
}
|
||||
let result = Coord::new(self.q, self.r);
|
||||
self.count += 1;
|
||||
self.q += 1;
|
||||
if self.r < 0 && self.q == self.radius + 1 {
|
||||
self.r += 1;
|
||||
self.q = -self.radius - self.r;
|
||||
}
|
||||
if self.r >= 0 && self.q + self.r == self.radius + 1 {
|
||||
self.r += 1;
|
||||
self.q = -self.radius;
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
|
||||
// blame OpenAI for this, and also Mozilla
|
||||
impl<'a, T> Iterator for CoordIterMut<'a, T> {
|
||||
impl<'a, T, I: Iterator<Item=Coord>> Iterator for CoordIterMut<'a, T, I> {
|
||||
type Item = (Coord, &'a mut T);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let coord = self.coords.next()?;
|
||||
let q = (coord.x + self.radius) as usize;
|
||||
let r = (coord.y + self.radius) as usize;
|
||||
let i = q + r * self.side_length;
|
||||
let i = q * self.side_length + r; // oops
|
||||
unsafe {
|
||||
// CoordIterator yields each valid map coordinate once, so each backing index is yielded once.
|
||||
Some((coord, &mut *self.data.add(i)))
|
||||
@@ -159,18 +126,18 @@ impl<T> Map<T> {
|
||||
q * self.side_length + r
|
||||
}
|
||||
|
||||
pub fn coordvec_to_index_offset(&self, c: CoordVec) -> isize {
|
||||
let q = c.x as isize;
|
||||
let r = c.y as isize;
|
||||
q * self.side_length as isize + r
|
||||
}
|
||||
|
||||
pub fn in_range(&self, coord: Coord) -> bool {
|
||||
hex_distance(coord, Coord::origin()) <= self.radius
|
||||
}
|
||||
|
||||
pub fn iter_coords(&self) -> impl Iterator<Item=Coord> {
|
||||
CoordIterator {
|
||||
radius: self.radius,
|
||||
count: 0,
|
||||
max: self.size(),
|
||||
r: -self.radius,
|
||||
q: 0
|
||||
}
|
||||
hex_range(self.radius).map(|c| Coord::new(c.x, c.y))
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item=(Coord, &T)> {
|
||||
@@ -183,13 +150,7 @@ impl<T> Map<T> {
|
||||
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item=(Coord, &mut T)> {
|
||||
CoordIterMut {
|
||||
coords: CoordIterator {
|
||||
radius: self.radius,
|
||||
count: 0,
|
||||
max: self.size(),
|
||||
r: -self.radius,
|
||||
q: 0
|
||||
},
|
||||
coords: self.iter_coords(),
|
||||
data: self.data.as_mut_ptr(),
|
||||
radius: self.radius,
|
||||
side_length: self.side_length,
|
||||
@@ -212,7 +173,7 @@ pub fn smooth(map: &Map<f32>, radius: i32) -> Map<f32> {
|
||||
//println!("{:?}", data[(map.radius as usize + 1, map.radius as usize)]);
|
||||
let mut kernel = Array2::from_elem((radius as usize * 2 + 1, radius as usize * 2 + 1), 0.0);
|
||||
|
||||
for (_, offset) in hex_range(radius) {
|
||||
for offset in hex_range(radius) {
|
||||
kernel[(offset.x as usize + radius as usize, offset.y as usize + radius as usize)] = 1.0 / count_hexes(radius) as f32;
|
||||
}
|
||||
|
||||
@@ -225,6 +186,79 @@ pub fn smooth(map: &Map<f32>, radius: i32) -> Map<f32> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn radius_1_conv_ref(inp: &Map<f32>, out: &mut Map<f32>, coefficients: &[f32; 7]) {
|
||||
for coord in inp.iter_coords() {
|
||||
let mut sum = 0.0;
|
||||
let mut count = 0.0;
|
||||
for (i, offset) in hex_range(1).enumerate() {
|
||||
//println!("{} {:?}", i, offset);
|
||||
let neighbor = coord + offset;
|
||||
if inp.in_range(neighbor) {
|
||||
sum += inp[neighbor] * coefficients[i];
|
||||
count += 1.0;
|
||||
}
|
||||
}
|
||||
out[coord] = sum / count;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: improve performance
|
||||
pub fn radius_1_conv(inp: &Map<f32>, out: &mut Map<f32>, coefficients: &[f32; 7]) {
|
||||
let offsets = hex_range(1).enumerate().collect::<Vec<_>>();
|
||||
let offset_offsets = hex_range(1).map(|c| inp.coordvec_to_index_offset(c)).enumerate().collect::<Vec<_>>();
|
||||
for coord in inp.iter_coords() {
|
||||
let i = inp.coord_to_index(coord);
|
||||
let range = hex_distance(coord, Coord::origin());
|
||||
let mut sum = 0.0;
|
||||
let mut count = 0.0;
|
||||
if range < inp.radius {
|
||||
for (k, offset) in &offset_offsets {
|
||||
sum += inp.data[(i as isize + *offset) as usize] * coefficients[*k];
|
||||
}
|
||||
count = 7.0;
|
||||
} else {
|
||||
for (i, offset) in &offsets {
|
||||
//println!("{} {:?}", i, offset);
|
||||
let neighbor = coord + *offset;
|
||||
if inp.in_range(neighbor) {
|
||||
sum += inp[neighbor] * coefficients[*i];
|
||||
count += 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
out.data[i] = sum / count;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{Map, radius_1_conv_ref, radius_1_conv};
|
||||
use test::bench::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn bench_r1_conv(b: &mut Bencher) {
|
||||
use std::hint::black_box;
|
||||
let map = black_box(Map::<f32>::from_fn(|_| fastrand::f32(), 512));
|
||||
let mut out_map = Map::new(512, 0.0f32);
|
||||
b.iter(|| {
|
||||
radius_1_conv(&map, &mut out_map, &[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_r1_conv() {
|
||||
let map = Map::<f32>::from_fn(|_| fastrand::f32(), 512);
|
||||
let mut out_map = Map::new(512, 0.0f32);
|
||||
let mut out_map_2 = Map::new(512, 0.0f32);
|
||||
radius_1_conv_ref(&map, &mut out_map, &[1.0, 1.5, -1.0, 2.0, 3.0, 0.1, 0.6]);
|
||||
radius_1_conv(&map, &mut out_map_2, &[1.0, 1.5, -1.0, 2.0, 3.0, 0.1, 0.6]);
|
||||
for coord in map.iter_coords() {
|
||||
assert!(out_map[coord] - out_map_2[coord] < 1e-6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<T> Index<Coord> for Map<T> {
|
||||
type Output = T;
|
||||
fn index(&self, index: Coord) -> &Self::Output {
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ impl Genome {
|
||||
if !self.terrain_valid(terrain) {
|
||||
return 0.0;
|
||||
}
|
||||
let mut water_diff = (water - sigmoid(self.optimal_water_level)).powf(2.0);
|
||||
let water_diff = (water - sigmoid(self.optimal_water_level)).powf(2.0);
|
||||
let temperature_diff = (temperature - sigmoid(self.optimal_temperature)).powf(2.0);
|
||||
let base = 1.0
|
||||
- self.nutrient_addition_rate() * 0.04 // nutrient enrichment has a growth tradeoff
|
||||
|
||||
+3
-3
@@ -19,7 +19,7 @@ pub mod config {
|
||||
pub const FIELD_DECAY_DELAY: u64 = 64;
|
||||
pub const PLANT_GROWTH_SCALE: f32 = 0.01;
|
||||
pub const SOIL_NUTRIENT_CONSUMPTION_RATE: f32 = 0.3;
|
||||
pub const SOIL_NUTRIENT_FIXATION_RATE: f32 = 0.03;
|
||||
pub const SOIL_NUTRIENT_FIXATION_RATE: f32 = 0.005;
|
||||
pub const WATER_CONSUMPTION_RATE: f32 = 0.05;
|
||||
pub const PLANT_IDLE_WATER_CONSUMPTION_SCALE: f32 = 0.02;
|
||||
pub const PLANT_DIEOFF_THRESHOLD: f32 = 0.3;
|
||||
@@ -30,12 +30,12 @@ pub mod config {
|
||||
pub const PLANT_POLLINATION_SCAN_FRACTION: f32 = 0.1;
|
||||
pub const PLANT_SEEDING_RADIUS: i32 = 5; // TODO: as above
|
||||
pub const PLANT_SEEDS_PER_EVENT: usize = 1;
|
||||
pub const SOIL_NUTRIENT_DECOMP_RETURN_RATE: f32 = 1.0 / SOIL_NUTRIENT_CONSUMPTION_RATE * 0.5;
|
||||
pub const SOIL_NUTRIENT_DECOMP_RETURN_RATE: f32 = 1.0 / SOIL_NUTRIENT_CONSUMPTION_RATE * 0.4;
|
||||
pub const PLANT_LIFESPAN_SCALE: f32 = 1.0 / PLANT_GROWTH_SCALE;
|
||||
pub const PLANT_REPRODUCTION_ATTEMPT_COST: f32 = 0.01;
|
||||
pub const INITIAL_PLANTS: usize = 131072;
|
||||
pub const EVOLUTION_RATE: f32 = 1.0;
|
||||
pub const CROWDING_MULTIPLIER: f32 = 0.3;
|
||||
pub const CROWDING_MULTIPLIER: f32 = 0.35;
|
||||
// Runtime game logic (misc)
|
||||
pub const VIEW: i32 = 15;
|
||||
pub const RANDOM_DESPAWN_INV_RATE: u64 = 4000;
|
||||
|
||||
+3
-17
@@ -304,7 +304,7 @@ pub fn simulate_water(heightmap: &mut Map<f32>, rain_map: &Map<f32>, sea: &HashS
|
||||
for point in path {
|
||||
let water_range_raw = watermap[point] * 1.0 - heightmap[point];
|
||||
let water_range = water_range_raw.ceil() as i32;
|
||||
for (_, nearby) in hex_range(water_range) {
|
||||
for nearby in hex_range(water_range) {
|
||||
if !watermap.in_range(point + nearby) {
|
||||
continue;
|
||||
}
|
||||
@@ -314,7 +314,8 @@ pub fn simulate_water(heightmap: &mut Map<f32>, rain_map: &Map<f32>, sea: &HashS
|
||||
|
||||
let erosion_range_raw = (water_range_raw * 2.0 + 2.0).powf(EROSION_EXPONENT);
|
||||
let erosion_range = erosion_range_raw.ceil() as i32;
|
||||
for (this_range, nearby) in hex_range(erosion_range) {
|
||||
for nearby in hex_range(erosion_range) {
|
||||
let this_range = hex_distance(point + nearby, point);
|
||||
if !watermap.in_range(point + nearby) {
|
||||
continue;
|
||||
}
|
||||
@@ -552,18 +553,3 @@ impl GeneratedWorld {
|
||||
self.heightmap.radius
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{Map, smooth, WORLD_RADIUS};
|
||||
use test::bench::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn bench_smooth_map(b: &mut Bencher) {
|
||||
use std::hint::black_box;
|
||||
b.iter(|| {
|
||||
let map = black_box(Map::new(WORLD_RADIUS, 0.0f32));
|
||||
smooth(&map, 3);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user