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 { initial_floor_chance: 0.45, iterations: 4, birth_limit: 5, death_limit: 4 }
14 }
15}
16
17pub struct CellularAutomata {
18 config: CellularConfig,
19}
20
21impl CellularAutomata {
22 pub fn new(config: CellularConfig) -> Self { Self { config } }
23}
24
25impl Default for CellularAutomata {
26 fn default() -> Self { Self::new(CellularConfig::default()) }
27}
28
29impl Algorithm<Tile> for CellularAutomata {
30 fn generate(&self, grid: &mut Grid<Tile>, seed: u64) {
31 let mut rng = Rng::new(seed);
32 let (w, h) = (grid.width(), grid.height());
33
34 for y in 1..h - 1 {
35 for x in 1..w - 1 {
36 if rng.chance(self.config.initial_floor_chance) {
37 grid.set(x as i32, y as i32, Tile::Floor);
38 }
39 }
40 }
41
42 for _ in 0..self.config.iterations {
43 let snapshot: Vec<bool> = (0..w * h)
44 .map(|i| grid[(i % w, i / w)].is_floor())
45 .collect();
46
47 for y in 1..h - 1 {
48 for x in 1..w - 1 {
49 let neighbors = count_neighbors(&snapshot, x, y, w);
50 let is_floor = snapshot[y * w + x];
51 let new_floor = if is_floor {
52 neighbors >= self.config.death_limit
53 } else {
54 neighbors >= self.config.birth_limit
55 };
56 grid.set(x as i32, y as i32, if new_floor { Tile::Floor } else { Tile::Wall });
57 }
58 }
59 }
60 }
61
62 fn name(&self) -> &'static str { "CellularAutomata" }
63}
64
65fn count_neighbors(cells: &[bool], x: usize, y: usize, w: usize) -> usize {
66 let mut count = 0;
67 for dy in -1i32..=1 {
68 for dx in -1i32..=1 {
69 if dx == 0 && dy == 0 { continue; }
70 let nx = (x as i32 + dx) as usize;
71 let ny = (y as i32 + dy) as usize;
72 if cells[ny * w + nx] { count += 1; }
73 }
74 }
75 count
76}