twmap/automapper/
execute.rs

1use ndarray::Array2;
2use vek::Vec2;
3
4use crate::automapper::{Automapper, Chance, Condition, Config, Rule, TileCondition};
5use crate::convert::TryTo;
6use crate::{AutomapperConfig, Tile, TilesLayer};
7
8struct DoubleView<'tiles> {
9    tiles: &'tiles mut Array2<Tile>,
10    copy: Option<Array2<Tile>>,
11}
12
13impl DoubleView<'_> {
14    fn view(&self) -> &Array2<Tile> {
15        match &self.copy {
16            Some(copy) => copy,
17            None => self.tiles,
18        }
19    }
20}
21
22impl TileCondition {
23    fn applies(&self, tile: Option<Tile>) -> bool {
24        match self {
25            TileCondition::Outside => tile.is_none(),
26            TileCondition::Index(id) => tile.map(|t| t.id) == Some(*id),
27            TileCondition::Tile(t) => tile == Some(*t),
28        }
29    }
30}
31
32impl Rule {
33    fn applies(&self, tiles: &Array2<Tile>, position: Vec2<usize>) -> bool {
34        let position_i64: Vec2<i64> = position.numcast().unwrap();
35        let offset_i64: Vec2<i64> = self.offset.numcast().unwrap();
36        let index = position_i64 + offset_i64;
37        let tile = if index.x < 0 || index.y < 0 {
38            None
39        } else {
40            let index: Vec2<usize> = index.numcast().unwrap();
41            tiles.get((index.y, index.x)).copied()
42        };
43
44        let id = tile.map(|t| t.id);
45        match &self.condition {
46            Condition::Empty => id == Some(0),
47            Condition::Full => id != Some(0),
48            Condition::WhiteList(list) => list.iter().any(|cond| cond.applies(tile)),
49            Condition::BlackList(list) => !list.iter().any(|cond| cond.applies(tile)),
50        }
51    }
52}
53
54const DEFAULT_RULE: Rule = Rule {
55    offset: Vec2::new(0, 0),
56    condition: Condition::Full,
57};
58
59fn hash_u32(mut n: u32) -> u32 {
60    n = n.wrapping_add(1);
61    n ^= n >> 17;
62    n = n.wrapping_mul(0xed5ad4bb);
63    n ^= n >> 11;
64    n = n.wrapping_mul(0xac4c1b51);
65    n ^= n >> 15;
66    n = n.wrapping_mul(0x31848bab);
67    n ^= n >> 14;
68    n
69}
70
71const HASH_MAX: u32 = 2_u32.pow(16);
72const PRIME: u32 = 31;
73
74fn hash_location(seed: u32, run: u32, rule: u32, x: u32, y: u32) -> u32 {
75    let mut hash: u32 = 1;
76
77    for n in [seed, run, rule, x, y] {
78        hash = hash.wrapping_mul(PRIME).wrapping_add(hash_u32(n));
79    }
80    hash = hash_u32(hash.wrapping_mul(PRIME));
81    hash % HASH_MAX
82}
83
84fn random_u32() -> u32 {
85    let mut u32_bytes: [u8; 4] = [0; 4];
86    getrandom::getrandom(&mut u32_bytes).unwrap();
87    u32::from_le_bytes(u32_bytes)
88}
89
90impl Chance {
91    fn chance(&self) -> Option<f32> {
92        match self {
93            Chance::Always => None,
94            Chance::OneOutOf(n) => Some(n.recip()),
95            Chance::Percentage(p) => Some(p / 100.),
96        }
97    }
98
99    fn u32_chance(&self) -> Option<u32> {
100        self.chance().map(|f| (f * HASH_MAX as f32) as u32)
101    }
102}
103
104impl Config {
105    pub fn run(&self, mut seed: u32, tiles: &mut Array2<Tile>) {
106        seed = match seed {
107            0 => random_u32(),
108            _ => seed,
109        };
110        let height = tiles.shape()[0];
111        let width = tiles.shape()[1];
112        for (run_i, run) in self.runs.iter().enumerate() {
113            let copy = run.layer_copy.then(|| tiles.clone());
114            let tilemap = DoubleView { tiles, copy };
115            for (rule_i, rule) in run.rules.iter().enumerate() {
116                let chance = rule.chance.u32_chance();
117                for y in 0..height {
118                    for x in 0..width {
119                        let position = Vec2::new(x, y);
120                        let default =
121                            !rule.default_rule || DEFAULT_RULE.applies(tilemap.view(), position);
122                        if default
123                            && rule
124                                .conditions
125                                .iter()
126                                .all(|cond| cond.applies(tilemap.view(), position))
127                        {
128                            if let Some(chance) = chance {
129                                if hash_location(
130                                    seed,
131                                    run_i.try_to(),
132                                    rule_i.try_to(),
133                                    x.try_to(),
134                                    y.try_to(),
135                                ) >= chance
136                                {
137                                    continue;
138                                }
139                            }
140                            tilemap.tiles[(y, x)] = rule.tile;
141                        }
142                    }
143                }
144            }
145        }
146    }
147}
148
149#[derive(Debug)]
150pub struct ConfigOutOfBounds;
151
152impl Automapper {
153    pub fn run(
154        &self,
155        automapper_config: &AutomapperConfig,
156        tiles: &mut Array2<Tile>,
157    ) -> Result<(), ConfigOutOfBounds> {
158        let index = match automapper_config.config {
159            None => return Ok(()),
160            Some(i) => i,
161        };
162        let config = match self.configs.get(index.try_to::<usize>()) {
163            None => return Err(ConfigOutOfBounds),
164            Some(c) => c,
165        };
166        config.run(automapper_config.seed, tiles);
167        Ok(())
168    }
169}
170
171impl TilesLayer {
172    /// Requires tiles to be loaded
173    pub fn run_automapper(&mut self, automapper: &Automapper) -> Result<(), ConfigOutOfBounds> {
174        automapper.run(&self.automapper_config, self.tiles.unwrap_mut())
175    }
176}