cellular_maps/
lib.rs

1use rand;
2
3use rand::Rng;
4
5pub struct CellularMap {
6    width: u32,
7    height: u32,
8    map: Vec<u8>,
9}
10
11pub enum EvolveStrategy {
12    Default, // The standard evolution rules.
13    Cleaning, // Aggressive cleaning. Remove a lot of "single" occupied tiles.
14}
15
16impl CellularMap {
17    /// Create a new `CellularMap` instance.
18    ///
19    /// # Arguments
20    ///
21    /// * `w` - The desired map width.
22    /// * `h` - The desired map height.
23    ///
24    /// # Example
25    ///
26    /// ```rust
27    /// use cellular_maps::CellularMap;
28    ///
29    /// // Create a 30x30 celular map.
30    /// let mut cm = CellularMap::new(30,30);
31    /// ```
32    ///
33    pub fn new(w: u32, h: u32) -> CellularMap {
34        let mut arraymap: Vec<u8> = Vec::with_capacity((w * h) as usize);
35        for _ in 0..w * h {
36            arraymap.push(0);
37        }
38        CellularMap {
39            width: w,
40            height: h,
41            map: arraymap,
42        }
43    }
44
45    /// Get the map width.
46    pub fn get_width(self: &CellularMap) -> u32 {
47        self.width
48    }
49
50    /// Get the map height.
51    pub fn get_height(self: &CellularMap) -> u32 {
52        self.height
53    }
54
55    /// Get the element in position `<r,c>`.
56    pub fn get_element(self: &CellularMap, r: u32, c: u32) -> u8 {
57        return self.map[self.get_index(r, c)];
58    }
59
60    /// Initialize a random `CellularMap`.
61    pub fn random_fill(self: &mut CellularMap, wall_prob: u32) {
62        for index in 0..self.width * self.height {
63            let (c, r) = (index % self.width, index / self.width);
64            self.map[index as usize] = if self.is_on_border(r, c) {
65                1
66            } else {
67                let map_middle = self.height / 2;
68                if r == map_middle {
69                    0
70                } else {
71                    let value = rand::thread_rng().gen_range(0, 100);
72                    if value < wall_prob {
73                        1
74                    } else {
75                        0
76                    }
77                }
78            };
79        }
80    }
81
82    pub fn evolve_default(self: &mut CellularMap) {
83        self.evolve(EvolveStrategy::Default)
84    }
85
86    /// Evolve the `CellularMap` according the automata rules.
87    pub fn evolve(self: &mut CellularMap, _strategy: EvolveStrategy) {
88        for r in 0..self.height {
89            for c in 0..self.width {
90                let value = self.place_logic(r, c);
91                let index = self.get_index(r, c);
92                self.map[index] = value;
93            }
94        }
95    }
96
97    /// Implements the wall evolution automata rules for a given position `<r,c>`.
98    fn place_logic(self: &mut CellularMap, r: u32, c: u32) -> u8 {
99        let num_wall1 = self.count_adjacent_wall(r, c, 1, 1);
100        let num_wall2 = self.count_adjacent_wall(r, c, 2, 2);
101
102        let index = self.get_index(r, c);
103        if self.map[index] == 1u8 {
104            if num_wall1 >= 3 {
105                1
106            } else {
107                0
108            }
109        } else {
110            if num_wall1 >= 5 || num_wall2 <= 2 {
111                1
112            } else {
113                0
114            }
115        }
116    }
117
118    /// Count the number of walls adjacent to `<r,c>` in a given radius `scopex` - `scopey`.
119    fn count_adjacent_wall(self: &mut CellularMap,
120                           r: u32,
121                           c: u32,
122                           scopex: u32,
123                           scopey: u32)
124                           -> u32 {
125        let endx = c + scopex + 1;
126        let endy = r + scopey + 1;
127
128        let startx = if scopex > c {
129            0
130        } else {
131            c - scopex
132        };
133        let underx = if scopex > c {
134            scopex - c
135        } else {
136            0
137        };
138
139        let starty = if scopey > r {
140            0
141        } else {
142            r - scopey
143        };
144        let undery = if scopey > r {
145            scopey - r
146        } else {
147            0
148        };
149
150        let mut wallcounter = underx * (2 * scopex + 1) + undery * (2 * scopey + 1) -
151                              undery * underx;
152
153        for iy in starty..endy {
154            for ix in startx..endx {
155                if (ix != c || iy != r) && self.is_wall(iy, ix) {
156                    wallcounter += 1;
157                }
158            }
159        }
160        return wallcounter;
161    }
162
163    /// Check if a given position `<r,c>` is a wall.
164    fn is_wall(self: &CellularMap, r: u32, c: u32) -> bool {
165        let index = self.get_index(r, c);
166        self.is_out_of_bound(r, c) || self.map[index] == 1
167    }
168
169    /// Check if a given position `<r,c>` is out of bound.
170    fn is_out_of_bound(self: &CellularMap, r: u32, c: u32) -> bool {
171        c > self.width - 1 || r > self.height - 1
172    }
173
174    /// Check if a given position `<r,c>` is on the map border.
175    fn is_on_border(self: &CellularMap, r: u32, c: u32) -> bool {
176        c == 0 || r == 0 || c == self.width - 1 || r == self.height - 1
177    }
178
179    /// Get the row-major index for the given position.
180    fn get_index(self: &CellularMap, r: u32, c: u32) -> usize {
181        (c + r * self.width) as usize
182    }
183}
184
185// Commented due to Rust Stable unstable ban madness.
186//
187
188#[test]
189fn constructor_test() {
190    let cm = CellularMap::new(12, 12);
191
192    assert!(12 == cm.width);
193    assert!(12 == cm.height);
194}
195
196#[test]
197fn get_element_test() {
198    let mut cm = CellularMap::new(12, 12);
199    cm.map[4] = 2u8;
200    assert_eq!(2u8, cm.get_element(0, 4));
201}
202
203// #[bench]
204// fn evolve_bench(b:&mut Bencher) {
205//     let mut cm = CellularMap::new(30,30);
206//     cm.random_fill(40);
207//     b.iter(|| {
208//         cm.evolve_default();
209//     });
210// }