dungeon/
dungeon.rs

1use {
2    simple_tiled_wfc::grid_generation::{WfcModule, WfcContext, WfcContextBuilder},
3    macroquad::prelude::{
4        scene::{Node, RefMut},
5        *
6    },
7    macroquad::miniquad::{TextureParams, TextureFormat, TextureWrap},
8    ron::de::from_reader,
9    std::{
10        collections::{VecDeque},
11        cmp::Ordering,
12        sync::mpsc::channel
13    }
14};
15mod serialization {
16    use serde::Deserialize;
17    use std::collections::HashMap;
18
19    #[derive(Debug, Deserialize, Clone, Copy)]
20    pub struct SubRect {
21        pub x: i32,
22        pub y: i32,
23        pub width: i32,
24        pub height: i32
25    }
26
27    #[derive(Debug, Deserialize, PartialEq, Eq, Clone, Copy)]
28    pub enum TileKind {
29        Wang4Corner(u8), // It is named as Wang4Corner,
30                         // but you don't have to provide all
31                         // four types of corners as well as
32                         // exhaustive sets are not required
33                         // (while still possible)
34        VerticalBridgeGroundVoid0x0,
35        VerticalBridgeGroundVoid0x1,
36        VerticalBridgeGroundVoid0x2,
37        VerticalBridgeGroundVoid1x0,
38        VerticalBridgeGroundVoid1x1,
39        VerticalBridgeGroundVoid1x2,
40        VerticalBridgeGroundVoid2x0,
41        VerticalBridgeGroundVoid2x1,
42        VerticalBridgeGroundVoid2x2,
43        VerticalBridgeWaterGround0x0,
44        VerticalBridgeWaterGround0x1,
45        VerticalBridgeWaterGround0x2,
46        VerticalBridgeWaterGround1x0,
47        VerticalBridgeWaterGround1x1,
48        VerticalBridgeWaterGround1x2,
49        VerticalBridgeWaterGround2x0,
50        VerticalBridgeWaterGround2x1,
51        VerticalBridgeWaterGround2x2,
52        HorizontalBridgeGroundVoid0x0,
53        HorizontalBridgeGroundVoid0x1,
54        HorizontalBridgeGroundVoid0x2,
55        HorizontalBridgeGroundVoid1x0,
56        HorizontalBridgeGroundVoid1x1,
57        HorizontalBridgeGroundVoid1x2,
58        HorizontalBridgeGroundVoid2x0,
59        HorizontalBridgeGroundVoid2x1,
60        HorizontalBridgeGroundVoid2x2,
61        HorizontalBridgeWaterGround0x0,
62        HorizontalBridgeWaterGround0x1,
63        HorizontalBridgeWaterGround0x2,
64        HorizontalBridgeWaterGround1x0,
65        HorizontalBridgeWaterGround1x1,
66        HorizontalBridgeWaterGround1x2,
67        HorizontalBridgeWaterGround2x0,
68        HorizontalBridgeWaterGround2x1,
69        HorizontalBridgeWaterGround2x2,
70    }
71
72    #[derive(Debug, Deserialize, Clone)]
73    pub struct Tile {
74        pub kind: TileKind,
75        pub subrects: Vec<SubRect>,
76        pub neighbours_east: Vec<TileKind>,
77        pub neighbours_west: Vec<TileKind>,
78        pub neighbours_north: Vec<TileKind>,
79        pub neighbours_south: Vec<TileKind>
80    }
81
82    #[derive(Debug, Deserialize)]
83    pub struct DungeonTiles {
84        pub tile_width: usize,
85        pub tile_height: usize,
86        pub wang_4_corner_tiles: HashMap<u8, Vec<SubRect>>,
87        pub extra_tiles: Vec<Tile>
88    }
89}
90use serialization::*;
91
92pub const fn get_north_east(kind_code: u8) -> u8 { kind_code & 0b11 }
93pub const fn get_north_west(kind_code: u8) -> u8 { (kind_code / 0b100) & 0b11 }
94pub const fn get_south_east(kind_code: u8) -> u8 { (kind_code / 0b10000) & 0b11 }
95pub const fn get_south_west(kind_code: u8) -> u8 { (kind_code / 0b1000000) & 0b11 }
96
97fn draw_subrect(tex: Texture2D, subrect: &SubRect, x: f32, y: f32, scale: usize) {
98    let InternalGlContext {
99        quad_context: ctx, ..
100    } = unsafe { get_internal_gl() };
101    draw_texture_ex(
102        tex,
103        x * ctx.dpi_scale(), y * ctx.dpi_scale(),
104        WHITE,
105        DrawTextureParams {
106            source: Some(Rect::new(
107                subrect.x as f32, subrect.y as f32,
108                subrect.width as f32, subrect.height as f32
109            )),
110            dest_size: Some([
111                subrect.width as f32 * ctx.dpi_scale() * scale as f32,
112                subrect.height as f32 * ctx.dpi_scale() * scale as f32
113            ].into()),
114            ..Default::default()
115        },
116    );
117}
118
119fn window_conf() -> Conf {
120    Conf {
121        window_title: "Dungeon".to_owned(),
122        fullscreen: false,
123        window_width: 1280,
124        window_height: 800,
125        high_dpi: true,
126        ..Default::default()
127    }
128}
129
130type CustomBitSet = [u8; 8];
131
132struct DungeonTilemap {
133    w: usize,
134    h: usize,
135    tile_width: usize,
136    tile_height: usize,
137    tiles: Vec<Tile>,
138    modules: Vec<WfcModule<CustomBitSet>>,
139    texture: Texture2D,
140    map_data: Vec<(usize, usize)>
141}
142
143impl DungeonTilemap {
144    pub async fn new(w: usize, h: usize) -> Self {
145        let InternalGlContext {
146            quad_context: ctx, ..
147        } = unsafe { get_internal_gl() };
148
149        let dungeon_bytes = load_file("assets/dungeon_tiles.png").await.unwrap();
150
151        let img = image::load_from_memory(&dungeon_bytes[..])
152            .unwrap_or_else(|e| panic!("{}", e))
153            .to_rgba8();
154
155        let img_w = img.width();
156        let img_h = img.height();
157
158        let texture = Texture2D::from_miniquad_texture(
159            miniquad::Texture::from_data_and_format(
160                ctx,
161                &img.into_raw(),
162                TextureParams {
163                    format: TextureFormat::RGBA8,
164                    wrap: TextureWrap::Clamp,
165                    filter: FilterMode::Nearest,
166                    width: img_w,
167                    height: img_h
168                }
169            )
170        );
171
172        let tiles_bytes = load_file("assets/dungeon_tiles.ron").await.unwrap();
173        let dungeon_tiles: DungeonTiles = from_reader(&tiles_bytes[..]).unwrap();
174
175        let mut tiles = Vec::new();
176        for (kind_code, subrects) in dungeon_tiles.wang_4_corner_tiles.iter() {
177            tiles.push(Tile {
178                kind: TileKind::Wang4Corner(*kind_code),
179                subrects: subrects.clone(),
180                neighbours_east: Vec::new(),
181                neighbours_west: Vec::new(),
182                neighbours_north: Vec::new(),
183                neighbours_south: Vec::new()
184            });
185        }
186        // We need to be sure that zero tile is always a "void" tile, so we doing an extra sort step
187        tiles.sort_by(|lhs, rhs| {
188            match (lhs.kind, rhs.kind) {
189                (TileKind::Wang4Corner(kind_lhs), TileKind::Wang4Corner(kind_rhs)) => kind_lhs.cmp(&kind_rhs),
190                _ => Ordering::Equal
191            }
192        });
193
194        for i in 0..tiles.len() {
195            let current_kind = tiles[i].kind;
196            for j in 0..tiles.len() {
197                let candidate_kind = tiles[j].kind;
198                let (matches_north, matches_south, matches_east, matches_west) = {
199                    match (current_kind, candidate_kind) {
200                        (TileKind::Wang4Corner(current), TileKind::Wang4Corner(candidate)) => {
201                            (
202                                get_north_east(current) == get_south_east(candidate) &&
203                                    get_north_west(current) == get_south_west(candidate),
204
205                                get_south_east(current) == get_north_east(candidate) &&
206                                    get_south_west(current) == get_north_west(candidate),
207
208                                get_north_east(current) == get_north_west(candidate) &&
209                                    get_south_east(current) == get_south_west(candidate),
210
211                                get_north_west(current) == get_north_east(candidate) &&
212                                    get_south_west(current) == get_south_east(candidate),
213                            )
214                        },
215                        _ => continue
216                    }
217                };
218                if matches_east {
219                    tiles[i].neighbours_east.push(candidate_kind);
220                }
221                if matches_west {
222                    tiles[i].neighbours_west.push(candidate_kind);
223                }
224                if matches_north {
225                    tiles[i].neighbours_north.push(candidate_kind);
226                }
227                if matches_south {
228                    tiles[i].neighbours_south.push(candidate_kind);
229                }
230            }
231        }
232
233        //Add bridges:
234        {
235            let bridge_tiles_offset = tiles.len();
236            tiles.extend_from_slice(&dungeon_tiles.extra_tiles[..]);
237
238            let mut bridge_match_queue_south = VecDeque::new();
239            let mut bridge_match_queue_north = VecDeque::new();
240            let mut bridge_match_queue_east = VecDeque::new();
241            let mut bridge_match_queue_west = VecDeque::new();
242
243            for bridge_tile in &tiles[bridge_tiles_offset..] {
244                for south_neighbour in bridge_tile.neighbours_south.iter() {
245                    if let TileKind::Wang4Corner(kind) = south_neighbour {
246                        for i in 0..bridge_tiles_offset {
247                            match tiles[i].kind {
248                                TileKind::Wang4Corner(kind_inner) if kind_inner == *kind => {
249                                    bridge_match_queue_south.push_back((bridge_tile.kind, i))
250                                },
251                                _ => ()
252                            };
253                        }
254                    }
255                }
256                for north_neighbour in bridge_tile.neighbours_north.iter() {
257                    if let TileKind::Wang4Corner(kind) = north_neighbour {
258                        for i in 0..bridge_tiles_offset {
259                            match tiles[i].kind {
260                                TileKind::Wang4Corner(kind_inner) if kind_inner == *kind => {
261                                    bridge_match_queue_north.push_back((bridge_tile.kind, i))
262                                },
263                                _ => ()
264                            };
265                        }
266                    }
267                }
268                for east_neighbour in bridge_tile.neighbours_east.iter() {
269                    if let TileKind::Wang4Corner(kind) = east_neighbour {
270                        for i in 0..bridge_tiles_offset {
271                            match tiles[i].kind {
272                                TileKind::Wang4Corner(kind_inner) if kind_inner == *kind => {
273                                    bridge_match_queue_east.push_back((bridge_tile.kind, i))
274                                },
275                                _ => ()
276                            };
277                        }
278                    }
279                }
280                for west_neighbour in bridge_tile.neighbours_west.iter() {
281                    if let TileKind::Wang4Corner(kind) = west_neighbour {
282                        for i in 0..bridge_tiles_offset {
283                            match tiles[i].kind {
284                                TileKind::Wang4Corner(kind_inner) if kind_inner == *kind => {
285                                    bridge_match_queue_west.push_back((bridge_tile.kind, i))
286                                },
287                                _ => ()
288                            };
289                        }
290                    }
291                }
292            }
293
294            while !bridge_match_queue_south.is_empty() {
295                let next_match = bridge_match_queue_south.pop_front().unwrap();
296                tiles[next_match.1].neighbours_north.push(next_match.0);
297            }
298
299            while !bridge_match_queue_north.is_empty() {
300                let next_match = bridge_match_queue_north.pop_front().unwrap();
301                tiles[next_match.1].neighbours_south.push(next_match.0);
302            }
303
304            while !bridge_match_queue_east.is_empty() {
305                let next_match = bridge_match_queue_east.pop_front().unwrap();
306                tiles[next_match.1].neighbours_west.push(next_match.0);
307            }
308
309            while !bridge_match_queue_west.is_empty() {
310                let next_match = bridge_match_queue_west.pop_front().unwrap();
311                tiles[next_match.1].neighbours_east.push(next_match.0);
312            }
313        }
314
315        let modules = tiles
316            .iter()
317            .map(|tile| {
318                let mut module: WfcModule<CustomBitSet> = WfcModule::new();
319                for i in 0..tiles.len() {
320                    if tile.neighbours_north.contains(&tiles[i].kind) {
321                        module.add_north_neighbour(i);
322                    }
323                    if tile.neighbours_south.contains(&tiles[i].kind) {
324                        module.add_south_neighbour(i);
325                    }
326                    if tile.neighbours_west.contains(&tiles[i].kind) {
327                        module.add_west_neighbour(i);
328                    }
329                    if tile.neighbours_east.contains(&tiles[i].kind) {
330                        module.add_east_neighbour(i);
331                    }
332                }
333                module
334            })
335            .collect::<Vec<_>>();
336        
337        Self {
338            w,
339            h,
340            tile_width: dungeon_tiles.tile_width,
341            tile_height: dungeon_tiles.tile_height,
342            tiles,
343            modules,
344            texture,
345            map_data: vec![(0, 0); w*h]
346        }
347    }
348
349    pub fn generate_new_map(&mut self) {
350        let mut wfc_context: WfcContext<CustomBitSet> = WfcContextBuilder
351            ::new(&self.modules, self.w, self.h)
352            .build();
353
354        let (tx, rc) = channel();
355
356        // Preset some fields in a map with a void to enforce more island-ish look
357        {
358            wfc_context.set_module(0, 0, 0);
359            wfc_context.set_module(self.h - 1, 0, 0);
360            wfc_context.set_module(0, self.w - 1, 0);
361            wfc_context.set_module(self.h - 1, self.w - 1, 0);
362            wfc_context.set_module(self.h / 2, self.w / 2, 0);
363        }
364
365        wfc_context.collapse(100, tx.clone());
366
367        let results = rc.recv()
368            .unwrap()
369            .unwrap_or_else(|_| vec![0; self.w * self.h]);
370
371        self.map_data.clear();
372        for &idx in results.iter() {
373            let random_id = rand::gen_range(0, self.tiles[idx].subrects.len());
374            self.map_data.push((idx, random_id));
375        }
376    }
377}
378
379impl Node for DungeonTilemap {
380    fn update(mut node: RefMut<Self>) {
381        if is_key_pressed(KeyCode::Space) {
382            node.generate_new_map();
383        }
384    }
385
386    fn draw(node: RefMut<Self>) {
387        let InternalGlContext {
388            quad_context: ctx, ..
389        } = unsafe { get_internal_gl() };
390
391        const SCALE_UP: usize = 2;
392        let start_x = screen_width() / ctx.dpi_scale();
393        let start_x = (start_x - (node.w * node.tile_width * SCALE_UP) as f32) / 2.0;
394
395        let start_y = screen_height() / ctx.dpi_scale();
396        let start_y = (start_y - (node.h * node.tile_height * SCALE_UP) as f32) / 2.0;
397
398        for j in 0..node.h {
399            for i in 0..node.w {
400                let idx = node.w * j + i;
401                let x = start_x + (node.tile_width * i * SCALE_UP) as f32;
402                let y = start_y + (node.tile_height * j * SCALE_UP) as f32;
403                let (idx, random_id) = node.map_data[idx];
404                draw_subrect(node.texture, &node.tiles[idx].subrects[random_id], x, y, SCALE_UP);
405            }
406        }
407    }
408}
409
410#[macroquad::main(window_conf)]
411async fn main() {
412    scene::add_node({
413        let mut tilemap = DungeonTilemap::new(48, 48).await;
414        tilemap.generate_new_map();
415        tilemap
416    });
417    loop {
418        if is_key_pressed(KeyCode::Escape) {
419            break;
420        }
421        clear_background(Color::new(0.12, 0.1, 0.15, 1.00));
422        next_frame().await;
423    }
424}