|
1 use integral_geometry::Size; |
|
2 use vec2d::Vec2D; |
|
3 use std::collections::HashMap; |
|
4 |
|
5 #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] |
|
6 pub enum Tile { |
|
7 Empty, |
|
8 Outside, |
|
9 Numbered(u32), |
|
10 } |
|
11 |
|
12 impl Tile { |
|
13 fn is(&self, i: u32) -> bool { |
|
14 *self == Tile::Numbered(i) |
|
15 } |
|
16 |
|
17 fn is_empty(&self) -> bool { |
|
18 match self { |
|
19 Tile::Empty => true, |
|
20 Tile::Outside => true, |
|
21 _ => false, |
|
22 } |
|
23 } |
|
24 |
|
25 fn is_empty_or(&self, i: u32) -> bool { |
|
26 match self { |
|
27 Tile::Numbered(n) => *n == i, |
|
28 Tile::Empty => true, |
|
29 _ => false, |
|
30 } |
|
31 } |
|
32 |
|
33 fn is_void_or(&self, i: u32) -> bool { |
|
34 match self { |
|
35 Tile::Numbered(n) => *n == i, |
|
36 _ => true, |
|
37 } |
|
38 } |
|
39 } |
|
40 |
|
41 impl Default for Tile { |
|
42 fn default() -> Self { |
|
43 Tile::Outside |
|
44 } |
|
45 } |
|
46 |
|
47 struct CollapseRule { |
|
48 to_tile: Tile, |
|
49 condition: fn([Tile; 4]) -> bool, |
|
50 } |
|
51 |
|
52 #[derive(Default)] |
|
53 pub struct WavefrontCollapse { |
|
54 rules: HashMap<Tile, Vec<CollapseRule>>, |
|
55 } |
|
56 |
|
57 impl WavefrontCollapse { |
|
58 pub fn generate_map<I: Iterator<Item = u32>, F: FnOnce(&mut Vec2D<Tile>)>( |
|
59 &mut self, |
|
60 map_size: &Size, |
|
61 seed_fn: F, |
|
62 random_numbers: &mut I, |
|
63 ) -> Vec2D<Tile> { |
|
64 let mut land = Vec2D::new(&map_size, Tile::Empty); |
|
65 |
|
66 seed_fn(&mut land); |
|
67 |
|
68 while self.collapse_step(&mut land, random_numbers) {} |
|
69 |
|
70 land |
|
71 } |
|
72 |
|
73 fn add_rule(&mut self, from_tile: Tile, to_tile: Tile, condition: fn([Tile; 4]) -> bool) { |
|
74 let rule = CollapseRule { to_tile, condition }; |
|
75 self.rules |
|
76 .entry(from_tile) |
|
77 .or_insert_with(Vec::new) |
|
78 .push(rule); |
|
79 } |
|
80 |
|
81 fn collapse_step<I: Iterator<Item = u32>>( |
|
82 &self, |
|
83 land: &mut Vec2D<Tile>, |
|
84 random_numbers: &mut I, |
|
85 ) -> bool { |
|
86 let mut collapse_occurred = false; |
|
87 for x in 0..land.width() { |
|
88 for y in 0..land.height() { |
|
89 let current_tile = land.get(y, x).expect("valid iteration range"); |
|
90 |
|
91 if let Some(rules) = self.rules.get(¤t_tile) { |
|
92 for rule in rules |
|
93 .iter() |
|
94 .cycle() |
|
95 .skip( |
|
96 random_numbers.next().unwrap_or_default() as usize % (rules.len() + 1), |
|
97 ) |
|
98 .take(rules.len()) |
|
99 { |
|
100 let neighbors = self.get_neighbors(&land, x, y); |
|
101 let have_neighbors = neighbors.iter().any(|t| !t.is_empty()); |
|
102 if have_neighbors && (rule.condition)(neighbors) { |
|
103 *land.get_mut(y, x).expect("valid iteration range") = rule.to_tile; |
|
104 collapse_occurred = true; |
|
105 break; |
|
106 } |
|
107 } |
|
108 } |
|
109 } |
|
110 } |
|
111 |
|
112 collapse_occurred |
|
113 } |
|
114 |
|
115 fn get_neighbors(&self, land: &Vec2D<Tile>, x: usize, y: usize) -> [Tile; 4] { |
|
116 [ |
|
117 land.get(y, x + 1).map(|p| *p).unwrap_or_default(), |
|
118 land.get(y + 1, x).map(|p| *p).unwrap_or_default(), |
|
119 land.get(y, x.wrapping_sub(1)).map(|p| *p).unwrap_or_default(), |
|
120 land.get(y.wrapping_sub(1), x).map(|p| *p).unwrap_or_default(), |
|
121 ] |
|
122 } |
|
123 } |
|
124 |
|
125 #[cfg(test)] |
|
126 mod tests { |
|
127 use super::{Tile, WavefrontCollapse}; |
|
128 use integral_geometry::Size; |
|
129 use vec2d::Vec2D; |
|
130 |
|
131 #[test] |
|
132 fn test_wavefront_collapse() { |
|
133 let size = Size::new(4, 4); |
|
134 let mut rnd = [0u32; 64].into_iter(); |
|
135 let mut wfc = WavefrontCollapse::default(); |
|
136 |
|
137 let empty_land = Vec2D::new(&size, Tile::Empty); |
|
138 let no_rules_land = wfc.generate_map(&size, |_| {}, &mut rnd); |
|
139 |
|
140 assert_eq!(empty_land.as_slice(), no_rules_land.as_slice()); |
|
141 |
|
142 wfc.add_rule(Tile::Empty, Tile::Numbered(0), |neighbors| { |
|
143 neighbors.iter().filter(|&n| *n == Tile::Empty).count() >= 2 |
|
144 }); |
|
145 let ruled_map = wfc.generate_map(&size, |_| {}, &mut rnd); |
|
146 |
|
147 assert_eq!(ruled_map.as_slice(), empty_land.as_slice()); |
|
148 } |
|
149 } |