use std::fmt::Write; pub const W: usize = 96; pub const H: usize = 64; pub const DAY: u64 = 1800; const WATER_DEEP: u8 = 0; const WATER: u8 = 1; const SAND: u8 = 2; const GRASS: u8 = 3; const FOREST: u8 = 4; const HILLS: u8 = 5; const MOUNTAIN: u8 = 6; const TREE: u8 = 1; const BERRY: u8 = 2; const STONE: u8 = 3; pub struct Rng(u64); impl Rng { fn next(&mut self) -> u64 { let mut x = self.0; x ^= x >> 12; x ^= x << 25; x ^= x >> 27; self.0 = x; x.wrapping_mul(0x2545F4914F6CDD1D) } fn f(&mut self) -> f32 { (self.next() >> 40) as f32 / (1u64 << 24) as f32 } fn chance(&mut self, p: f32) -> bool { self.f() < p } fn below(&mut self, n: usize) -> usize { (self.next() % n as u64) as usize } } fn hash(x: i32, y: i32, seed: u64) -> f32 { let mut h = (x as u64).wrapping_mul(0x9E3779B97F4A7C15) ^ (y as u64).wrapping_mul(0xC2B2AE3D27D4EB4F) ^ seed.wrapping_mul(0x165667B19E3779F9); h ^= h >> 33; h = h.wrapping_mul(0xFF51AFD7ED558CCD); h ^= h >> 33; (h >> 40) as f32 / (1u64 << 24) as f32 } fn value_noise(x: f32, y: f32, seed: u64) -> f32 { let (xi, yi) = (x.floor() as i32, y.floor() as i32); let (fx, fy) = (x - xi as f32, y - yi as f32); let s = |t: f32| t * t * (3.0 - 2.0 * t); let (u, v) = (s(fx), s(fy)); let a = hash(xi, yi, seed); let b = hash(xi + 1, yi, seed); let c = hash(xi, yi + 1, seed); let d = hash(xi + 1, yi + 1, seed); let top = a + (b - a) * u; let bot = c + (d - c) * u; top + (bot - top) * v } fn fbm(x: f32, y: f32, seed: u64) -> f32 { let (mut sum, mut amp, mut freq, mut norm) = (0.0, 1.0, 1.0 / 16.0, 0.0); for o in 0..4 { sum += amp * value_noise(x * freq, y * freq, seed + o); norm += amp; amp *= 0.5; freq *= 2.0; } sum / norm } #[derive(Clone)] pub struct Person { id: u32, x: usize, y: usize, hunger: f32, food: u32, wood: u32, stone: u32, action: &'static str, dead: bool, } impl Person { fn carry(&self) -> u32 { self.food + self.wood + self.stone } } #[derive(Clone)] pub struct Animal { x: usize, y: usize, energy: f32, dead: bool, } pub struct World { tiles: Vec, res: Vec<(u8, u16)>, people: Vec, rabbits: Vec, wolves: Vec, houses: Vec<(usize, usize)>, home: (usize, usize), food: u32, wood: u32, stone: u32, tools: u32, rng: Rng, tick: u64, next_id: u32, log: Vec, } fn cheb(a: (usize, usize), b: (usize, usize)) -> usize { a.0.abs_diff(b.0).max(a.1.abs_diff(b.1)) } impl World { pub fn new(seed: u64) -> World { let mut tiles = vec![0u8; W * H]; for y in 0..H { for x in 0..W { let dx = (x as f32 / W as f32 - 0.5) * 2.0; let dy = (y as f32 / H as f32 - 0.5) * 2.0; let d = (dx * dx + dy * dy).min(1.5); let h = fbm(x as f32, y as f32, seed) + 0.25 - 0.55 * d; let m = fbm(x as f32 + 500.0, y as f32 + 500.0, seed + 99); tiles[y * W + x] = if h < 0.28 { WATER_DEEP } else if h < 0.36 { WATER } else if h < 0.41 { SAND } else if h < 0.68 { if m > 0.52 { FOREST } else { GRASS } } else if h < 0.78 { HILLS } else { MOUNTAIN }; } } let mut w = World { tiles, res: vec![(0, 0); W * H], people: vec![], rabbits: vec![], wolves: vec![], houses: vec![], home: (W / 2, H / 2), food: 4, wood: 0, stone: 0, tools: 0, rng: Rng(seed.wrapping_mul(0x9E3779B97F4A7C15) | 1), tick: DAY / 4, next_id: 1, log: vec![], }; w.home = w .find_land(GRASS) .unwrap_or_else(|| w.find_land(SAND).unwrap_or((W / 2, H / 2))); for i in 0..W * H { let r = w.rng.f(); w.res[i] = match w.tiles[i] { FOREST if r < 0.6 => (TREE, 5), GRASS if r < 0.04 => (BERRY, 4), HILLS if r < 0.15 => (STONE, 6), _ => (0, 0), }; } let (hx, hy) = w.home; w.res[hy * W + hx] = (0, 0); for _ in 0..6 { w.spawn_person(); } for _ in 0..60 { if let Some(p) = w.random_land() { w.rabbits.push(Animal { x: p.0, y: p.1, energy: 70.0, dead: false, }); } } for _ in 0..4 { if let Some(p) = w.random_land() { if cheb(p, w.home) > 15 { w.wolves.push(Animal { x: p.0, y: p.1, energy: 70.0, dead: false, }); } } } w } fn find_land(&self, kind: u8) -> Option<(usize, usize)> { let c = (W / 2, H / 2); let mut best: Option<(usize, usize)> = None; for y in 0..H { for x in 0..W { if self.tiles[y * W + x] == kind && best.is_none_or(|b| cheb((x, y), c) < cheb(b, c)) { best = Some((x, y)); } } } best } fn random_land(&mut self) -> Option<(usize, usize)> { for _ in 0..50 { let (x, y) = (self.rng.below(W), self.rng.below(H)); if self.walkable(x as i32, y as i32) { return Some((x, y)); } } None } fn spawn_person(&mut self) { let (x, y) = self.home; self.people.push(Person { id: self.next_id, x, y, hunger: 10.0, food: 0, wood: 0, stone: 0, action: "idle", dead: false, }); self.next_id += 1; } fn say(&mut self, s: String) { self.log .push(format!("[day {}] {}", self.tick / DAY + 1, s)); if self.log.len() > 8 { self.log.remove(0); } } pub fn daylight(&self) -> f32 { let phase = (self.tick % DAY) as f32 / DAY as f32; let v = -(phase * std::f32::consts::TAU).cos(); (0.5 + v * 0.8).clamp(0.0, 1.0) } fn walkable(&self, x: i32, y: i32) -> bool { if x < 0 || y < 0 || x >= W as i32 || y >= H as i32 { return false; } matches!( self.tiles[y as usize * W + x as usize], SAND | GRASS | FOREST | HILLS ) } fn move_dir(&mut self, x: usize, y: usize, dx: i32, dy: i32) -> (usize, usize) { for (a, b) in [(dx, dy), (dx, 0), (0, dy)] { if (a, b) != (0, 0) && self.walkable(x as i32 + a, y as i32 + b) { return ((x as i32 + a) as usize, (y as i32 + b) as usize); } } self.wander(x, y) } fn wander(&mut self, x: usize, y: usize) -> (usize, usize) { for _ in 0..4 { let (a, b) = (self.rng.below(3) as i32 - 1, self.rng.below(3) as i32 - 1); if (a, b) != (0, 0) && self.walkable(x as i32 + a, y as i32 + b) { return ((x as i32 + a) as usize, (y as i32 + b) as usize); } } (x, y) } fn move_toward(&mut self, from: (usize, usize), to: (usize, usize)) -> (usize, usize) { let dx = (to.0 as i32 - from.0 as i32).signum(); let dy = (to.1 as i32 - from.1 as i32).signum(); self.move_dir(from.0, from.1, dx, dy) } fn nearest_res(&self, from: (usize, usize), kind: u8, r: usize) -> Option<(usize, usize)> { let mut best: Option<(usize, usize)> = None; let (x0, x1) = (from.0.saturating_sub(r), (from.0 + r).min(W - 1)); let (y0, y1) = (from.1.saturating_sub(r), (from.1 + r).min(H - 1)); for y in y0..=y1 { for x in x0..=x1 { if self.res[y * W + x].0 == kind && best.is_none_or(|b| cheb((x, y), from) < cheb(b, from)) { best = Some((x, y)); } } } best } fn nearest_animal(list: &[Animal], from: (usize, usize), r: usize) -> Option { let mut best: Option<(usize, usize)> = None; for (i, a) in list.iter().enumerate() { let d = cheb((a.x, a.y), from); if !a.dead && d <= r && best.is_none_or(|(_, bd)| d < bd) { best = Some((i, d)); } } best.map(|b| b.0) } pub fn step(&mut self) { self.tick += 1; self.regrow(); if self.tick % 2 == 0 { for i in 0..self.people.len() { self.tick_person(i); } for i in 0..self.rabbits.len() { self.tick_rabbit(i); } } else { for i in 0..self.wolves.len() { self.tick_wolf(i); } } self.people.retain(|p| !p.dead); self.rabbits.retain(|a| !a.dead); self.wolves.retain(|a| !a.dead); self.population(); } fn regrow(&mut self) { for _ in 0..20 { let i = self.rng.below(W * H); if self.res[i].0 != 0 { continue; } let p = self.rng.f(); match self.tiles[i] { FOREST if p < 0.02 => self.res[i] = (TREE, 5), GRASS if p < 0.02 => self.res[i] = (BERRY, 4), HILLS if p < 0.004 => self.res[i] = (STONE, 6), _ => {} } } } fn population(&mut self) { if self.tick % 400 == 0 && self.food >= 10 && self.people.len() < 8 + self.houses.len() * 3 { self.food -= 6; self.spawn_person(); self.say("A child was born".into()); } if self.tick % 50 == 0 { if self.rabbits.len() < 15 { if let Some(p) = self.random_land() { self.rabbits.push(Animal { x: p.0, y: p.1, energy: 70.0, dead: false, }); } } if self.wolves.len() < 2 && self.rabbits.len() > 40 { if let Some(p) = self.random_land() { if cheb(p, self.home) > 15 { self.wolves.push(Animal { x: p.0, y: p.1, energy: 70.0, dead: false, }); } } } } } fn tick_person(&mut self, i: usize) { let mut p = self.people[i].clone(); let night = self.daylight() < 0.2; let pos = (p.x, p.y); let at_home = cheb(pos, self.home) <= 1; p.hunger += if night && at_home { 0.04 } else { 0.1 }; if p.hunger >= 100.0 { p.dead = true; self.say(format!("Person #{} starved", p.id)); self.people[i] = p; return; } if at_home { self.food += p.food; self.wood += p.wood; self.stone += p.stone; p.food = 0; p.wood = 0; p.stone = 0; if p.hunger > 40.0 && self.food > 0 { self.food -= 1; p.hunger = (p.hunger - 25.0).max(0.0); } if self.wood >= 3 && self.stone >= 1 && self.tools < self.people.len() as u32 { self.wood -= 3; self.stone -= 1; self.tools += 1; } if self.wood >= 12 && self.stone >= 4 && self.houses.len() < 10 { let (hx, hy) = self.home; let x = (hx as i32 + self.rng.below(13) as i32 - 6).clamp(0, W as i32 - 1); let y = (hy as i32 + self.rng.below(13) as i32 - 6).clamp(0, H as i32 - 1); if self.walkable(x, y) && self.res[y as usize * W + x as usize].0 == 0 && !self.houses.contains(&(x as usize, y as usize)) { self.wood -= 12; self.stone -= 4; self.houses.push((x as usize, y as usize)); self.say("A new house was built".into()); } } } if p.hunger > 50.0 && p.food > 0 { p.food -= 1; p.hunger = (p.hunger - 25.0).max(0.0); } let home = self.home; let yield_n = if self.tools >= self.people.len() as u32 { 2 } else { 1 }; let mut target: Option<(usize, usize)> = None; let mut gather: Option = None; let mut hunt = false; if night { if at_home { p.action = "sleeping"; } else { p.action = "going home"; target = Some(home); } } else if p.hunger > 50.0 && p.food == 0 && self.food > 0 { p.action = "going to eat"; target = Some(home); } else if p.carry() >= 6 { p.action = "hauling goods"; target = Some(home); } else { let order: [u8; 3] = if self.food < 12 || p.hunger > 50.0 { [BERRY, TREE, STONE] } else if self.wood <= self.stone * 2 + 8 { [TREE, STONE, BERRY] } else { [STONE, TREE, BERRY] }; for k in order { if let Some(t) = self.nearest_res(pos, k, 25) { target = Some(t); gather = Some(k); p.action = match k { BERRY => "foraging", TREE => "chopping wood", _ => "mining stone", }; break; } } if (self.food < 12 || p.hunger > 50.0) && (gather != Some(BERRY) || self.rng.chance(0.3)) { if let Some(r) = Self::nearest_animal(&self.rabbits, pos, 25) { target = Some((self.rabbits[r].x, self.rabbits[r].y)); hunt = true; gather = None; p.action = "hunting"; } } } if let Some(t) = target { if hunt { if cheb(pos, t) <= 1 { if let Some(r) = Self::nearest_animal(&self.rabbits, pos, 1) { if self.rng.chance(0.5) { self.rabbits[r].dead = true; p.food += 3; } } } else { (p.x, p.y) = self.move_toward(pos, t); } } else if let Some(k) = gather { let idx = pos.1 * W + pos.0; if pos == t && self.res[idx].0 == k { let take = (yield_n as u16).min(self.res[idx].1); self.res[idx].1 -= take; if self.res[idx].1 == 0 { self.res[idx].0 = 0; } match k { BERRY => p.food += take as u32, TREE => p.wood += take as u32, _ => p.stone += take as u32, } } else { (p.x, p.y) = self.move_toward(pos, t); } } else if !at_home || p.action == "going to eat" && cheb(pos, t) > 1 { (p.x, p.y) = self.move_toward(pos, t); } } else if p.action != "sleeping" { p.action = "wandering"; (p.x, p.y) = self.wander(p.x, p.y); } self.people[i] = p; } fn tick_rabbit(&mut self, i: usize) { let mut a = self.rabbits[i].clone(); a.energy -= 0.05; if matches!(self.tiles[a.y * W + a.x], GRASS | FOREST) { a.energy = (a.energy + 0.2).min(100.0); } let pos = (a.x, a.y); if let Some(w) = Self::nearest_animal(&self.wolves, pos, 4) { if self.rng.chance(0.8) { let dx = (a.x as i32 - self.wolves[w].x as i32).signum(); let dy = (a.y as i32 - self.wolves[w].y as i32).signum(); (a.x, a.y) = self.move_dir(a.x, a.y, dx, dy); } } else if self.rng.chance(0.5) { (a.x, a.y) = self.wander(a.x, a.y); } if a.energy > 80.0 && self.rabbits.len() < 120 && self.rng.chance(0.01) { a.energy -= 30.0; self.rabbits.push(Animal { x: a.x, y: a.y, energy: 50.0, dead: false, }); } if a.energy <= 0.0 { a.dead = true; } self.rabbits[i] = a; } fn tick_wolf(&mut self, i: usize) { let mut a = self.wolves[i].clone(); a.energy -= 0.15; let pos = (a.x, a.y); if let Some(r) = Self::nearest_animal(&self.rabbits, pos, 10) { let t = (self.rabbits[r].x, self.rabbits[r].y); if cheb(pos, t) <= 1 { if self.rng.chance(0.4) { self.rabbits[r].dead = true; a.energy = (a.energy + 50.0).min(100.0); } } else { (a.x, a.y) = self.move_toward(pos, t); } } else { (a.x, a.y) = self.wander(a.x, a.y); } if a.energy > 90.0 && self.wolves.len() < 10 && self.rng.chance(0.02) { a.energy -= 40.0; self.wolves.push(Animal { x: a.x, y: a.y, energy: 50.0, dead: false, }); } if a.energy <= 0.0 { a.dead = true; } self.wolves[i] = a; } pub fn terrain_json(&self) -> String { let tiles: String = self.tiles.iter().map(|t| (b'0' + t) as char).collect(); format!("{{\"w\":{},\"h\":{},\"tiles\":\"{}\"}}", W, H, tiles) } pub fn state_json(&self) -> String { let mut s = String::new(); let _ = write!( s, "{{\"tick\":{},\"day\":{},\"hour\":{:.2},\"daylight\":{:.3},\"stock\":{{\"food\":{},\"wood\":{},\"stone\":{},\"tools\":{}}},\"home\":[{},{}],", self.tick, self.tick / DAY + 1, (self.tick % DAY) as f32 / DAY as f32 * 24.0, self.daylight(), self.food, self.wood, self.stone, self.tools, self.home.0, self.home.1 ); s.push_str("\"houses\":["); let j: Vec = self .houses .iter() .map(|h| format!("[{},{}]", h.0, h.1)) .collect(); s.push_str(&j.join(",")); s.push_str("],\"people\":["); let j: Vec = self .people .iter() .map(|p| { format!( "[{},{},{},\"{}\",{:.0},{},{},{}]", p.id, p.x, p.y, p.action, p.hunger, p.food, p.wood, p.stone ) }) .collect(); s.push_str(&j.join(",")); s.push_str("],\"rabbits\":["); let j: Vec = self .rabbits .iter() .map(|a| format!("[{},{},{:.0}]", a.x, a.y, a.energy)) .collect(); s.push_str(&j.join(",")); s.push_str("],\"wolves\":["); let j: Vec = self .wolves .iter() .map(|a| format!("[{},{},{:.0}]", a.x, a.y, a.energy)) .collect(); s.push_str(&j.join(",")); s.push_str("],\"res\":["); let mut first = true; for (i, r) in self.res.iter().enumerate() { if r.0 != 0 { if !first { s.push(','); } first = false; let _ = write!(s, "[{},{},{},{}]", i % W, i / W, r.0, r.1); } } s.push_str("],\"log\":["); let j: Vec = self.log.iter().map(|l| format!("\"{}\"", l)).collect(); s.push_str(&j.join(",")); s.push_str("]}"); s } }