terrain_forge/algorithms/
cellular.rs1use crate::{Algorithm, Grid, Rng, Tile};
2
3#[derive(Debug, Clone)]
4pub struct CellularConfig {
5 pub initial_floor_chance: f64,
6 pub iterations: usize,
7 pub birth_limit: usize,
8 pub death_limit: usize,
9}
10
11impl Default for CellularConfig {
12 fn default() -> Self {
13 Self {
14 initial_floor_chance: 0.45,
15 iterations: 4,
16 birth_limit: 5,
17 death_limit: 4,
18 }
19 }
20}
21
22pub struct CellularAutomata {
23 config: CellularConfig,
24}
25
26impl CellularAutomata {
27 pub fn new(config: CellularConfig) -> Self {
28 Self { config }
29 }
30}
31
32impl Default for CellularAutomata {
33 fn default() -> Self {
34 Self::new(CellularConfig::default())
35 }
36}
37
38impl Algorithm<Tile> for CellularAutomata {
39 fn generate(&self, grid: &mut Grid<Tile>, seed: u64) {
40 let mut rng = Rng::new(seed);
41 let (w, h) = (grid.width(), grid.height());
42
43 for y in 1..h - 1 {
44 for x in 1..w - 1 {
45 if rng.chance(self.config.initial_floor_chance) {
46 grid.set(x as i32, y as i32, Tile::Floor);
47 }
48 }
49 }
50
51 for _ in 0..self.config.iterations {
52 let snapshot: Vec<bool> = (0..w * h)
53 .map(|i| grid[(i % w, i / w)].is_floor())
54 .collect();
55
56 for y in 1..h - 1 {
57 for x in 1..w - 1 {
58 let neighbors = count_neighbors(&snapshot, x, y, w);
59 let is_floor = snapshot[y * w + x];
60 let new_floor = if is_floor {
61 neighbors >= self.config.death_limit
62 } else {
63 neighbors >= self.config.birth_limit
64 };
65 grid.set(
66 x as i32,
67 y as i32,
68 if new_floor { Tile::Floor } else { Tile::Wall },
69 );
70 }
71 }
72 }
73 }
74
75 fn name(&self) -> &'static str {
76 "CellularAutomata"
77 }
78}
79
80fn count_neighbors(cells: &[bool], x: usize, y: usize, w: usize) -> usize {
81 let mut count = 0;
82 for dy in -1i32..=1 {
83 for dx in -1i32..=1 {
84 if dx == 0 && dy == 0 {
85 continue;
86 }
87 let nx = (x as i32 + dx) as usize;
88 let ny = (y as i32 + dy) as usize;
89 if cells[ny * w + nx] {
90 count += 1;
91 }
92 }
93 }
94 count
95}