├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── assets
├── Hack-Regular.ttf
├── mapgen.wasm
└── tilesheet_colored.png
├── index.html
└── src
├── fundamentals.rs
├── main.rs
├── maptools.rs
├── procgen
├── bsp_tree.rs
├── cellular_automata.rs
├── maze_with_rooms.rs
├── mod.rs
├── room_placement.rs
├── rwalk.rs
└── tunneling.rs
└── utils.rs
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "adler32"
7 | version = "1.2.0"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
10 |
11 | [[package]]
12 | name = "ahash"
13 | version = "0.4.7"
14 | source = "registry+https://github.com/rust-lang/crates.io-index"
15 | checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
16 |
17 | [[package]]
18 | name = "audir-sles"
19 | version = "0.1.0"
20 | source = "registry+https://github.com/rust-lang/crates.io-index"
21 | checksum = "ea47348666a8edb7ad80cbee3940eb2bccf70df0e6ce09009abe1a836cb779f5"
22 |
23 | [[package]]
24 | name = "audrey"
25 | version = "0.3.0"
26 | source = "registry+https://github.com/rust-lang/crates.io-index"
27 | checksum = "58b92a84e89497e3cd25d3672cd5d1c288abaac02c18ff21283f17d118b889b8"
28 | dependencies = [
29 | "dasp_frame",
30 | "dasp_sample",
31 | "hound",
32 | "lewton",
33 | ]
34 |
35 | [[package]]
36 | name = "autocfg"
37 | version = "1.1.0"
38 | source = "registry+https://github.com/rust-lang/crates.io-index"
39 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
40 |
41 | [[package]]
42 | name = "bitflags"
43 | version = "1.3.2"
44 | source = "registry+https://github.com/rust-lang/crates.io-index"
45 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
46 |
47 | [[package]]
48 | name = "bumpalo"
49 | version = "3.9.1"
50 | source = "registry+https://github.com/rust-lang/crates.io-index"
51 | checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
52 |
53 | [[package]]
54 | name = "bytemuck"
55 | version = "1.9.1"
56 | source = "registry+https://github.com/rust-lang/crates.io-index"
57 | checksum = "cdead85bdec19c194affaeeb670c0e41fe23de31459efd1c174d049269cf02cc"
58 |
59 | [[package]]
60 | name = "byteorder"
61 | version = "1.4.3"
62 | source = "registry+https://github.com/rust-lang/crates.io-index"
63 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
64 |
65 | [[package]]
66 | name = "cc"
67 | version = "1.0.73"
68 | source = "registry+https://github.com/rust-lang/crates.io-index"
69 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
70 |
71 | [[package]]
72 | name = "cfg-if"
73 | version = "1.0.0"
74 | source = "registry+https://github.com/rust-lang/crates.io-index"
75 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
76 |
77 | [[package]]
78 | name = "color_quant"
79 | version = "1.1.0"
80 | source = "registry+https://github.com/rust-lang/crates.io-index"
81 | checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
82 |
83 | [[package]]
84 | name = "crc32fast"
85 | version = "1.3.2"
86 | source = "registry+https://github.com/rust-lang/crates.io-index"
87 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
88 | dependencies = [
89 | "cfg-if",
90 | ]
91 |
92 | [[package]]
93 | name = "dasp_frame"
94 | version = "0.11.0"
95 | source = "registry+https://github.com/rust-lang/crates.io-index"
96 | checksum = "b2a3937f5fe2135702897535c8d4a5553f8b116f76c1529088797f2eee7c5cd6"
97 | dependencies = [
98 | "dasp_sample",
99 | ]
100 |
101 | [[package]]
102 | name = "dasp_sample"
103 | version = "0.11.0"
104 | source = "registry+https://github.com/rust-lang/crates.io-index"
105 | checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
106 |
107 | [[package]]
108 | name = "deflate"
109 | version = "0.8.6"
110 | source = "registry+https://github.com/rust-lang/crates.io-index"
111 | checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
112 | dependencies = [
113 | "adler32",
114 | "byteorder",
115 | ]
116 |
117 | [[package]]
118 | name = "fontdue"
119 | version = "0.5.2"
120 | source = "registry+https://github.com/rust-lang/crates.io-index"
121 | checksum = "c75712fff1702bac51b7eaa5a5ca9f9853b8055ef5906088a32f4fe196595a1d"
122 | dependencies = [
123 | "hashbrown",
124 | "ttf-parser",
125 | ]
126 |
127 | [[package]]
128 | name = "glam"
129 | version = "0.14.0"
130 | source = "registry+https://github.com/rust-lang/crates.io-index"
131 | checksum = "333928d5eb103c5d4050533cec0384302db6be8ef7d3cebd30ec6a35350353da"
132 |
133 | [[package]]
134 | name = "hashbrown"
135 | version = "0.9.1"
136 | source = "registry+https://github.com/rust-lang/crates.io-index"
137 | checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
138 | dependencies = [
139 | "ahash",
140 | ]
141 |
142 | [[package]]
143 | name = "hound"
144 | version = "3.4.0"
145 | source = "registry+https://github.com/rust-lang/crates.io-index"
146 | checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549"
147 |
148 | [[package]]
149 | name = "image"
150 | version = "0.23.14"
151 | source = "registry+https://github.com/rust-lang/crates.io-index"
152 | checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
153 | dependencies = [
154 | "bytemuck",
155 | "byteorder",
156 | "color_quant",
157 | "num-iter",
158 | "num-rational",
159 | "num-traits",
160 | "png",
161 | ]
162 |
163 | [[package]]
164 | name = "lewton"
165 | version = "0.9.4"
166 | source = "registry+https://github.com/rust-lang/crates.io-index"
167 | checksum = "8d542c1a317036c45c2aa1cf10cc9d403ca91eb2d333ef1a4917e5cb10628bd0"
168 | dependencies = [
169 | "byteorder",
170 | "ogg",
171 | "smallvec",
172 | ]
173 |
174 | [[package]]
175 | name = "libc"
176 | version = "0.2.126"
177 | source = "registry+https://github.com/rust-lang/crates.io-index"
178 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
179 |
180 | [[package]]
181 | name = "macroquad"
182 | version = "0.3.16"
183 | source = "registry+https://github.com/rust-lang/crates.io-index"
184 | checksum = "6b723ade71357d07177c769af9ae42beff73c854e827ee5a4ec765dd2beb95e1"
185 | dependencies = [
186 | "bumpalo",
187 | "fontdue",
188 | "glam",
189 | "image",
190 | "macroquad_macro",
191 | "miniquad",
192 | "quad-rand",
193 | "quad-snd",
194 | ]
195 |
196 | [[package]]
197 | name = "macroquad_macro"
198 | version = "0.1.7"
199 | source = "registry+https://github.com/rust-lang/crates.io-index"
200 | checksum = "f5cecfede1e530599c8686f7f2d609489101d3d63741a6dc423afc997ce3fcc8"
201 |
202 | [[package]]
203 | name = "mapgen"
204 | version = "0.1.0"
205 | dependencies = [
206 | "macroquad",
207 | ]
208 |
209 | [[package]]
210 | name = "maybe-uninit"
211 | version = "2.0.0"
212 | source = "registry+https://github.com/rust-lang/crates.io-index"
213 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
214 |
215 | [[package]]
216 | name = "miniquad"
217 | version = "0.3.0-alpha.46"
218 | source = "registry+https://github.com/rust-lang/crates.io-index"
219 | checksum = "c1462459fb1813decc7115ab4aed8f41183612557b08bf604bfb46b05286d703"
220 | dependencies = [
221 | "sapp-android",
222 | "sapp-darwin",
223 | "sapp-dummy",
224 | "sapp-ios",
225 | "sapp-linux",
226 | "sapp-wasm",
227 | "sapp-windows",
228 | ]
229 |
230 | [[package]]
231 | name = "miniz_oxide"
232 | version = "0.3.7"
233 | source = "registry+https://github.com/rust-lang/crates.io-index"
234 | checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
235 | dependencies = [
236 | "adler32",
237 | ]
238 |
239 | [[package]]
240 | name = "ndk-sys"
241 | version = "0.2.2"
242 | source = "registry+https://github.com/rust-lang/crates.io-index"
243 | checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121"
244 |
245 | [[package]]
246 | name = "num-integer"
247 | version = "0.1.45"
248 | source = "registry+https://github.com/rust-lang/crates.io-index"
249 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
250 | dependencies = [
251 | "autocfg",
252 | "num-traits",
253 | ]
254 |
255 | [[package]]
256 | name = "num-iter"
257 | version = "0.1.43"
258 | source = "registry+https://github.com/rust-lang/crates.io-index"
259 | checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
260 | dependencies = [
261 | "autocfg",
262 | "num-integer",
263 | "num-traits",
264 | ]
265 |
266 | [[package]]
267 | name = "num-rational"
268 | version = "0.3.2"
269 | source = "registry+https://github.com/rust-lang/crates.io-index"
270 | checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
271 | dependencies = [
272 | "autocfg",
273 | "num-integer",
274 | "num-traits",
275 | ]
276 |
277 | [[package]]
278 | name = "num-traits"
279 | version = "0.2.15"
280 | source = "registry+https://github.com/rust-lang/crates.io-index"
281 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
282 | dependencies = [
283 | "autocfg",
284 | ]
285 |
286 | [[package]]
287 | name = "ogg"
288 | version = "0.7.1"
289 | source = "registry+https://github.com/rust-lang/crates.io-index"
290 | checksum = "13e571c3517af9e1729d4c63571a27edd660ade0667973bfc74a67c660c2b651"
291 | dependencies = [
292 | "byteorder",
293 | ]
294 |
295 | [[package]]
296 | name = "png"
297 | version = "0.16.8"
298 | source = "registry+https://github.com/rust-lang/crates.io-index"
299 | checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
300 | dependencies = [
301 | "bitflags",
302 | "crc32fast",
303 | "deflate",
304 | "miniz_oxide",
305 | ]
306 |
307 | [[package]]
308 | name = "quad-alsa-sys"
309 | version = "0.3.2"
310 | source = "registry+https://github.com/rust-lang/crates.io-index"
311 | checksum = "c66c2f04a6946293477973d85adc251d502da51c57b08cd9c997f0cfd8dcd4b5"
312 | dependencies = [
313 | "libc",
314 | ]
315 |
316 | [[package]]
317 | name = "quad-rand"
318 | version = "0.2.1"
319 | source = "registry+https://github.com/rust-lang/crates.io-index"
320 | checksum = "658fa1faf7a4cc5f057c9ee5ef560f717ad9d8dc66d975267f709624d6e1ab88"
321 |
322 | [[package]]
323 | name = "quad-snd"
324 | version = "0.2.3"
325 | source = "registry+https://github.com/rust-lang/crates.io-index"
326 | checksum = "86e0b4259cfd6a317a46df7b7cb4c09a08ba150642e6f6fb7df5a6b3450a0a29"
327 | dependencies = [
328 | "audir-sles",
329 | "audrey",
330 | "libc",
331 | "quad-alsa-sys",
332 | "winapi",
333 | ]
334 |
335 | [[package]]
336 | name = "sapp-android"
337 | version = "0.1.10"
338 | source = "registry+https://github.com/rust-lang/crates.io-index"
339 | checksum = "9c0d0e6f562c01c533f693ac9c045d69cbeab24d2e16caaaa0e67d06ae6e0940"
340 | dependencies = [
341 | "libc",
342 | "ndk-sys",
343 | ]
344 |
345 | [[package]]
346 | name = "sapp-darwin"
347 | version = "0.1.8"
348 | source = "registry+https://github.com/rust-lang/crates.io-index"
349 | checksum = "f1282369f486bc334efa2d76fa4d54c20b37664d83da14fc60afbe941814e374"
350 | dependencies = [
351 | "cc",
352 | ]
353 |
354 | [[package]]
355 | name = "sapp-dummy"
356 | version = "0.1.5"
357 | source = "registry+https://github.com/rust-lang/crates.io-index"
358 | checksum = "66f1ad26a5b6c682b9ca27c66db9aa91002b8d98a82ac7101ded57285215a478"
359 | dependencies = [
360 | "libc",
361 | ]
362 |
363 | [[package]]
364 | name = "sapp-ios"
365 | version = "0.1.2"
366 | source = "registry+https://github.com/rust-lang/crates.io-index"
367 | checksum = "081e6e5261c9ac2e938979b6a854a53b439f065fc3c897205ce7e69d3028b4a9"
368 | dependencies = [
369 | "cc",
370 | ]
371 |
372 | [[package]]
373 | name = "sapp-linux"
374 | version = "0.1.14"
375 | source = "registry+https://github.com/rust-lang/crates.io-index"
376 | checksum = "dc71510a7781d8f31d58046a9117682c8e8546ad43f52c0e31275db214495c3d"
377 | dependencies = [
378 | "libc",
379 | ]
380 |
381 | [[package]]
382 | name = "sapp-wasm"
383 | version = "0.1.26"
384 | source = "registry+https://github.com/rust-lang/crates.io-index"
385 | checksum = "00e859e8645a3bcb85aecd40bab883438e4105f21b21bccbeac2348760f508bb"
386 |
387 | [[package]]
388 | name = "sapp-windows"
389 | version = "0.2.20"
390 | source = "registry+https://github.com/rust-lang/crates.io-index"
391 | checksum = "088f921b591fdcabf5f2dc17237be5203b6a1a6988f252d36e153f1442a8138e"
392 | dependencies = [
393 | "winapi",
394 | ]
395 |
396 | [[package]]
397 | name = "smallvec"
398 | version = "0.6.14"
399 | source = "registry+https://github.com/rust-lang/crates.io-index"
400 | checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0"
401 | dependencies = [
402 | "maybe-uninit",
403 | ]
404 |
405 | [[package]]
406 | name = "ttf-parser"
407 | version = "0.12.3"
408 | source = "registry+https://github.com/rust-lang/crates.io-index"
409 | checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6"
410 |
411 | [[package]]
412 | name = "winapi"
413 | version = "0.3.9"
414 | source = "registry+https://github.com/rust-lang/crates.io-index"
415 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
416 | dependencies = [
417 | "winapi-i686-pc-windows-gnu",
418 | "winapi-x86_64-pc-windows-gnu",
419 | ]
420 |
421 | [[package]]
422 | name = "winapi-i686-pc-windows-gnu"
423 | version = "0.4.0"
424 | source = "registry+https://github.com/rust-lang/crates.io-index"
425 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
426 |
427 | [[package]]
428 | name = "winapi-x86_64-pc-windows-gnu"
429 | version = "0.4.0"
430 | source = "registry+https://github.com/rust-lang/crates.io-index"
431 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
432 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "mapgen"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | macroquad = "0.3.16"
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 optimistic-nihilist
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mapgen
2 | A small collection of procedural dungeon generation algorithms.
3 | Built with Rust & [macroquad](https://github.com/not-fl3/macroquad).
4 | WebAssembly build deployed [here](https://optimistic-nihilist.github.io/mapgen).
5 |
6 | Animated version (separate repo so I could deploy it too using github pages): [mapgen-animated](https://github.com/optimistic-nihilist/mapgen-animated)
7 | deployed [here](https://optimistic-nihilist.github.io/mapgen-animated/)
8 |
9 | ### Run
10 | Clone the repository, then `cargo run --release`.
11 |
12 | ### Build WASM
13 | `cargo build --release --target wasm32-unknown-unknown` produces `mapgen.wasm` under `target/wasm32-unknown-unknown/release`.
14 | Read [this](https://github.com/not-fl3/macroquad#wasm) for a detailed example on what to do with it.
15 |
16 | ### Credits
17 | - Heavily inspired (and on occasion, downright stolen from) https://github.com/AtTheMatinee/dungeon-generation
18 | - Tiles are from 0x72's amazing 16x16 dungeon tileset ([v1](https://0x72.itch.io/16x16-dungeon-tileset) & [v2](https://0x72.itch.io/dungeontileset-ii))
19 | - Algorithms (sometimes loosely, oftentimes lousily) based on:
20 | - Tunneling algorithm: [roguebasin](http://www.roguebasin.com/index.php/Complete_Roguelike_Tutorial,_using_python%2Blibtcod,_part_3)
21 | - BSP tree: [roguebasin](http://www.roguebasin.com/index.php/Basic_BSP_Dungeon_generation)
22 | - Random walk: [roguebasin](http://www.roguebasin.com/index.php/Random_Walk_Cave_Generation)
23 | - Cellular automata: [roguebasin](http://www.roguebasin.com/index.php/Cellular_Automata_Method_for_Generating_Random_Cave-Like_Levels)
24 | - Room placement: [rockpapershotgun](https://www.rockpapershotgun.com/how-do-roguelikes-generate-levels)
25 | - Maze with rooms: [journal.stuffwithstuff.com](http://journal.stuffwithstuff.com/2014/12/21/rooms-and-mazes/)
26 |
--------------------------------------------------------------------------------
/assets/Hack-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optimistic-nihilist/mapgen/95039478c1a92e6f034b835444a6fbf8eecaa1b4/assets/Hack-Regular.ttf
--------------------------------------------------------------------------------
/assets/mapgen.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optimistic-nihilist/mapgen/95039478c1a92e6f034b835444a6fbf8eecaa1b4/assets/mapgen.wasm
--------------------------------------------------------------------------------
/assets/tilesheet_colored.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optimistic-nihilist/mapgen/95039478c1a92e6f034b835444a6fbf8eecaa1b4/assets/tilesheet_colored.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | mapgen
6 |
35 |
36 |
37 |
38 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/fundamentals.rs:
--------------------------------------------------------------------------------
1 | pub const WINW: i32 = 1120;
2 | pub const WINH: i32 = 640;
3 | pub const TILESIZE: i32 = 16;
4 | pub const COLS: i32 = WINW / TILESIZE;
5 | pub const ROWS: i32 = WINH / TILESIZE;
6 |
7 | pub const BSPTREE_LEAF_MIN_SIZE: i32 = 10;
8 | pub const BSPTREE_ROOM_MIN_SIZE: i32 = 6;
9 | pub const BSPTREE_ROOM_MAX_SIZE: i32 = 25;
10 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | mod fundamentals;
2 | mod maptools;
3 | mod procgen;
4 | mod utils;
5 | use fundamentals::{COLS, ROWS, TILESIZE, WINH, WINW};
6 | use macroquad::prelude::*;
7 | use maptools::{new_map, randomize_map, Map, TileType};
8 | use procgen::bsp_tree::BSPTreeGenerator;
9 | use procgen::cellular_automata::CellularAutomataGenerator;
10 | use procgen::maze_with_rooms::MazeGenerator;
11 | use procgen::room_placement::RoomPlacementGenerator;
12 | use procgen::rwalk::RandomWalkGenerator;
13 | use procgen::tunneling::TunnelingGenerator;
14 | use std::collections::HashMap;
15 |
16 | fn window_conf() -> Conf {
17 | Conf {
18 | window_title: "mapgen".to_owned(),
19 | fullscreen: false,
20 | window_resizable: true,
21 | window_width: WINW,
22 | window_height: WINH,
23 | ..Default::default()
24 | }
25 | }
26 |
27 | fn render_map(tiles: &HashMap, texture: Texture2D, m: &Map) {
28 | for row in 0..ROWS {
29 | for col in 0..COLS {
30 | let curr_tile = m[row as usize][col as usize];
31 | let curr_type = match curr_tile {
32 | 0 => TileType::Wall,
33 | 1 => TileType::Floor,
34 | _ => TileType::Hero,
35 | };
36 | draw_texture_ex(
37 | texture,
38 | col as f32 * TILESIZE as f32,
39 | row as f32 * TILESIZE as f32,
40 | WHITE,
41 | tiles.get(&curr_type).unwrap().clone(),
42 | );
43 | }
44 | }
45 | }
46 |
47 | fn render_help_full(params: TextParams) {
48 | const HELP_TEXT: [&str; 4] = [
49 | "[r] - randomize map [1] - tunneling [5] - room placement",
50 | "[c] - clear map [2] - BSP [6] - maze with rooms",
51 | "[f] - toggle FPS [3] - random walk [LShift+num] - frenzy",
52 | "[h] - toggle help [4] - cell. automata [ESC] - quit",
53 | ];
54 | for (idx, row) in HELP_TEXT.iter().enumerate() {
55 | draw_text_ex(
56 | row,
57 | 10.0,
58 | WINH as f32 - (HELP_TEXT.len() as f32 - 1.0 - idx as f32) * 19.0 - 16.0,
59 | params,
60 | );
61 | }
62 | }
63 |
64 | #[macroquad::main(window_conf)]
65 | async fn main() {
66 | // seed PRNG
67 | rand::srand(macroquad::miniquad::date::now() as _);
68 |
69 | // load texture tile sheet
70 | let texture: Texture2D = load_texture("assets/tilesheet_colored.png").await.unwrap();
71 | texture.set_filter(FilterMode::Nearest);
72 |
73 | // setup DrawTextureParams for individual tile types
74 | let tile_wall: DrawTextureParams = DrawTextureParams {
75 | source: Some(Rect::new(
76 | 0.0 * TILESIZE as f32,
77 | 0.0,
78 | TILESIZE as f32,
79 | TILESIZE as f32,
80 | )),
81 | ..Default::default()
82 | };
83 | let tile_floor: DrawTextureParams = DrawTextureParams {
84 | source: Some(Rect::new(
85 | 1.0 * TILESIZE as f32,
86 | 0.0,
87 | TILESIZE as f32,
88 | TILESIZE as f32,
89 | )),
90 | ..Default::default()
91 | };
92 | let tile_hero: DrawTextureParams = DrawTextureParams {
93 | source: Some(Rect::new(
94 | 2.0 * TILESIZE as f32,
95 | 0.0,
96 | TILESIZE as f32,
97 | TILESIZE as f32,
98 | )),
99 | ..Default::default()
100 | };
101 |
102 | // store each tiles' DrawTextureParams with their respective TileType as key
103 | let tiles: HashMap = HashMap::from([
104 | (TileType::Wall, tile_wall),
105 | (TileType::Floor, tile_floor),
106 | (TileType::Hero, tile_hero),
107 | ]);
108 |
109 | // load font
110 | let font: Font = load_ttf_font("assets/Hack-Regular.ttf").await.unwrap();
111 | let font_params: TextParams = TextParams {
112 | font,
113 | font_size: 16,
114 | ..Default::default()
115 | };
116 |
117 | // create initial empty map
118 | let mut map = new_map(TileType::Floor);
119 |
120 | let mut measurement_start_time = get_time();
121 | let mut current_fps = get_fps();
122 |
123 | let mut show_help = true;
124 | let mut show_fps = true;
125 |
126 | // main loop
127 | loop {
128 | let bg = Color::from_rgba(40, 40, 40, 255);
129 | clear_background(bg);
130 |
131 | render_map(&tiles, texture, &map);
132 |
133 | if show_fps {
134 | draw_text_ex(&format!("FPS: {}", current_fps), 10.0, 26.0, font_params);
135 | }
136 |
137 | if show_help {
138 | render_help_full(font_params);
139 | } else {
140 | let mut gray_text = font_params.clone();
141 | gray_text.color = Color::from_rgba(160, 160, 160, 255);
142 | draw_text_ex("[h]", 10.0, WINH as f32 - 16.0, gray_text);
143 | }
144 |
145 | if is_key_pressed(KeyCode::H) {
146 | show_help = !show_help;
147 | }
148 | if is_key_pressed(KeyCode::F) {
149 | show_fps = !show_fps;
150 | }
151 | if is_key_pressed(KeyCode::C) {
152 | map = new_map(TileType::Floor);
153 | }
154 | if is_key_pressed(KeyCode::R) {
155 | map = randomize_map();
156 | }
157 | if is_key_pressed(KeyCode::Key1) {
158 | map = TunnelingGenerator::generate_map(6, 16, 30);
159 | }
160 | if is_key_pressed(KeyCode::Key2) {
161 | map = BSPTreeGenerator::generate_map();
162 | }
163 | if is_key_pressed(KeyCode::Key3) {
164 | map = RandomWalkGenerator::generate_map();
165 | }
166 | if is_key_pressed(KeyCode::Key4) {
167 | map = CellularAutomataGenerator::generate_map();
168 | }
169 | if is_key_pressed(KeyCode::Key5) {
170 | map = RoomPlacementGenerator::generate_map();
171 | }
172 | if is_key_pressed(KeyCode::Key6) {
173 | map = MazeGenerator::generate_map();
174 | }
175 | if is_key_pressed(KeyCode::Escape) {
176 | break;
177 | }
178 |
179 | if is_key_down(KeyCode::LeftShift) {
180 | if is_key_down(KeyCode::R) {
181 | map = randomize_map();
182 | }
183 | if is_key_down(KeyCode::Key1) {
184 | map = TunnelingGenerator::generate_map(6, 16, 30);
185 | }
186 | if is_key_down(KeyCode::Key2) {
187 | map = BSPTreeGenerator::generate_map();
188 | }
189 | if is_key_down(KeyCode::Key3) {
190 | map = RandomWalkGenerator::generate_map();
191 | }
192 | if is_key_down(KeyCode::Key4) {
193 | map = CellularAutomataGenerator::generate_map();
194 | }
195 | if is_key_down(KeyCode::Key5) {
196 | map = RoomPlacementGenerator::generate_map();
197 | }
198 | if is_key_down(KeyCode::Key6) {
199 | map = MazeGenerator::generate_map();
200 | }
201 | }
202 |
203 | // update FPS counter roughly every 0.5 second
204 | if get_time() - measurement_start_time > 0.5 {
205 | measurement_start_time = get_time();
206 | current_fps = get_fps();
207 | }
208 | next_frame().await
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/src/maptools.rs:
--------------------------------------------------------------------------------
1 | use crate::fundamentals::*;
2 | use crate::utils::*;
3 |
4 | #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
5 | pub enum TileType {
6 | Wall,
7 | Floor,
8 | Hero,
9 | }
10 |
11 | pub type Map = [[i32; COLS as usize]; ROWS as usize];
12 |
13 | pub fn new_map(fill_with: TileType) -> Map {
14 | [[fill_with as i32; COLS as usize]; ROWS as usize]
15 | }
16 |
17 | pub fn randomize_map() -> Map {
18 | let mut map = [[0; COLS as usize]; ROWS as usize];
19 | for row in 0..ROWS {
20 | for col in 0..COLS {
21 | map[row as usize][col as usize] = randr(0..2);
22 | }
23 | }
24 | map
25 | }
26 |
27 | #[derive(Debug)]
28 | pub struct Rect {
29 | pub x: i32,
30 | pub y: i32,
31 | pub w: i32,
32 | pub h: i32,
33 | }
34 |
35 | #[derive(Copy, Clone, Debug)]
36 | pub struct Room {
37 | pub x1: i32,
38 | pub x2: i32,
39 | pub y1: i32,
40 | pub y2: i32,
41 | }
42 |
43 | impl Room {
44 | pub fn new(x: i32, y: i32, w: i32, h: i32) -> Self {
45 | Self {
46 | x1: x,
47 | x2: x + w,
48 | y1: y,
49 | y2: y + h,
50 | }
51 | }
52 |
53 | pub fn center(&self) -> (i32, i32) {
54 | let cx = (self.x1 + self.x2) / 2;
55 | let cy = (self.y1 + self.y2) / 2;
56 | (cx, cy)
57 | }
58 |
59 | pub fn overlaps(&self, other: Room) -> bool {
60 | self.x1 <= other.x2 && self.x2 >= other.x1 && self.y1 <= other.y2 && self.y2 >= other.y1
61 | }
62 |
63 | pub fn carve(&self, m: &mut Map) {
64 | for x in self.x1..self.x2 {
65 | for y in self.y1..self.y2 {
66 | m[y as usize][x as usize] = TileType::Floor as i32;
67 | }
68 | }
69 | }
70 | }
71 |
72 | pub fn carve_horz_tunnel(m: &mut Map, x1: i32, x2: i32, y: i32) {
73 | let min_x = std::cmp::min(x1, x2);
74 | let max_x = std::cmp::max(x1, x2);
75 | for x in min_x..max_x + 1 {
76 | m[y as usize][x as usize] = TileType::Floor as i32;
77 | }
78 | }
79 |
80 | pub fn carve_vert_tunnel(m: &mut Map, y1: i32, y2: i32, x: i32) {
81 | let min_y = std::cmp::min(y1, y2);
82 | let max_y = std::cmp::max(y1, y2);
83 | for y in min_y..max_y + 1 {
84 | m[y as usize][x as usize] = TileType::Floor as i32;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/procgen/bsp_tree.rs:
--------------------------------------------------------------------------------
1 | use crate::{fundamentals::*, maptools::*, utils::*};
2 |
3 | #[derive(Clone, Debug)]
4 | struct BSPNode {
5 | x: i32,
6 | y: i32,
7 | w: i32,
8 | h: i32,
9 | room: Option,
10 | left_child: Option>,
11 | right_child: Option>,
12 | }
13 |
14 | impl BSPNode {
15 | fn new(
16 | x: i32,
17 | y: i32,
18 | w: i32,
19 | h: i32,
20 | room: Option,
21 | left: Option>,
22 | right: Option>,
23 | ) -> Self {
24 | Self {
25 | x,
26 | y,
27 | w,
28 | h,
29 | room,
30 | left_child: left,
31 | right_child: right,
32 | }
33 | }
34 |
35 | fn split(&mut self) -> bool {
36 | if self.left_child.is_some() && self.right_child.is_some() {
37 | // node is already split
38 | return false;
39 | }
40 |
41 | let mut hsplit = true;
42 | let ratio = self.w as f32 / self.h as f32;
43 | if ratio > 1.25 {
44 | hsplit = false;
45 | }
46 |
47 | let max = match hsplit {
48 | true => self.h - BSPTREE_LEAF_MIN_SIZE,
49 | false => self.w - BSPTREE_LEAF_MIN_SIZE,
50 | };
51 |
52 | if max <= BSPTREE_LEAF_MIN_SIZE {
53 | // node too small to split further
54 | return false;
55 | }
56 |
57 | let split = randr(BSPTREE_LEAF_MIN_SIZE..max);
58 |
59 | if hsplit {
60 | self.left_child = Some(Box::new(BSPNode::new(
61 | self.x, self.y, self.w, split, None, None, None,
62 | )));
63 | self.right_child = Some(Box::new(BSPNode::new(
64 | self.x,
65 | self.y + split,
66 | self.w,
67 | self.h - split,
68 | None,
69 | None,
70 | None,
71 | )));
72 | } else {
73 | self.left_child = Some(Box::new(BSPNode::new(
74 | self.x, self.y, split, self.h, None, None, None,
75 | )));
76 | self.right_child = Some(Box::new(BSPNode::new(
77 | self.x + split,
78 | self.y,
79 | self.w - split,
80 | self.h,
81 | None,
82 | None,
83 | None,
84 | )));
85 | }
86 |
87 | true
88 | }
89 | }
90 |
91 | fn carve_leafs(curr: &mut BSPNode, map: &mut Map) {
92 | if curr.left_child.is_some() || curr.right_child.is_some() {
93 | if curr.left_child.is_some() {
94 | carve_leafs(curr.left_child.as_mut().unwrap(), map);
95 | }
96 | if curr.right_child.is_some() {
97 | carve_leafs(curr.right_child.as_mut().unwrap(), map);
98 | }
99 | if let (Some(l), Some(r)) = (curr.left_child.as_mut(), curr.right_child.as_mut()) {
100 | let lroom = get_room(l).unwrap();
101 | let rroom = get_room(r).unwrap();
102 |
103 | let (lx, ly) = lroom.center();
104 | let (rx, ry) = rroom.center();
105 |
106 | if randr(0..1) == 1 {
107 | carve_horz_tunnel(map, lx, rx, ly);
108 | carve_vert_tunnel(map, ly, ry, rx);
109 | } else {
110 | carve_vert_tunnel(map, ly, ry, lx);
111 | carve_horz_tunnel(map, lx, rx, ry);
112 | }
113 | }
114 | } else {
115 | if curr.room.is_none() {
116 | let w = randr(BSPTREE_ROOM_MIN_SIZE..std::cmp::min(BSPTREE_ROOM_MAX_SIZE, curr.w - 1));
117 | let h = randr(BSPTREE_ROOM_MIN_SIZE..std::cmp::min(BSPTREE_ROOM_MAX_SIZE, curr.h - 1));
118 | let x = randr(curr.x..curr.x + (curr.w - 1) - w);
119 | let y = randr(curr.y..curr.y + (curr.h - 1) - h);
120 |
121 | curr.room = Some(Room::new(x, y, w, h));
122 | curr.room.unwrap().carve(map);
123 | }
124 | }
125 | }
126 |
127 | fn get_room(curr: &mut BSPNode) -> Option {
128 | if let Some(r) = curr.room {
129 | return Some(r);
130 | }
131 | if curr.left_child.is_some() {
132 | return get_room(curr.left_child.as_mut().unwrap());
133 | }
134 | if curr.right_child.is_some() {
135 | return get_room(curr.right_child.as_mut().unwrap());
136 | }
137 | None
138 | }
139 |
140 | fn split_until_fail(curr: &mut BSPNode) {
141 | if !curr.split() {
142 | return;
143 | }
144 | split_until_fail(curr.left_child.as_mut().unwrap());
145 | split_until_fail(curr.right_child.as_mut().unwrap());
146 | }
147 |
148 | pub struct BSPTreeGenerator {}
149 |
150 | impl BSPTreeGenerator {
151 | pub fn generate_map() -> Map {
152 | let mut map = new_map(TileType::Wall);
153 | let mut root = BSPNode::new(1, 1, COLS, ROWS, None, None, None);
154 | split_until_fail(&mut root);
155 | carve_leafs(&mut root, &mut map);
156 | map
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/procgen/cellular_automata.rs:
--------------------------------------------------------------------------------
1 | use crate::{fundamentals::*, maptools::*, utils::*};
2 |
3 | const DEATH_LIMIT: i32 = 3;
4 | const BIRTH_LIMIT: i32 = 4;
5 |
6 | fn randomize_map_seal_edges(m: &mut Map) {
7 | for y in 0..ROWS {
8 | for x in 0..COLS {
9 | if (x == 0) | (x == COLS - 1) | (y == 0) | (y == ROWS - 1) {
10 | m[y as usize][x as usize] = TileType::Wall as i32;
11 | } else {
12 | m[y as usize][x as usize] = match randr(0..100) {
13 | 0..=32 => TileType::Wall as i32,
14 | _ => TileType::Floor as i32,
15 | };
16 | }
17 | }
18 | }
19 | }
20 |
21 | pub fn evolve_map(m: &mut Map) {
22 | for y in 0..ROWS {
23 | for x in 0..COLS {
24 | let neighbor_count = count_alive_neighbors(m, x, y);
25 | if m[y as usize][x as usize] == TileType::Wall as i32 {
26 | if neighbor_count < DEATH_LIMIT {
27 | m[y as usize][x as usize] = TileType::Floor as i32
28 | }
29 | } else {
30 | if BIRTH_LIMIT < neighbor_count {
31 | m[y as usize][x as usize] = TileType::Wall as i32
32 | }
33 | }
34 | }
35 | }
36 | }
37 |
38 | fn count_alive_neighbors(m: &Map, x: i32, y: i32) -> i32 {
39 | let mut count = 0;
40 | for i in -1..2 {
41 | for j in -1..2 {
42 | let nx = x + i;
43 | let ny = y + j;
44 | if (nx < 0) | (nx >= COLS) | (ny < 0) | (ny >= ROWS) {
45 | count += 1;
46 | } else if m[ny as usize][nx as usize] == TileType::Wall as i32 {
47 | count += 1;
48 | }
49 | }
50 | }
51 | count
52 | }
53 | fn get_random_cave_size(m: &mut Map) -> i32 {
54 | // get random floor starting position for DFS
55 | let mut rx = randr(0..COLS);
56 | let mut ry = randr(0..ROWS);
57 | while m[ry as usize][rx as usize] != TileType::Floor as i32 {
58 | rx = randr(0..COLS);
59 | ry = randr(0..ROWS);
60 | }
61 | let mut visited = new_map(TileType::Wall);
62 | dfs(rx, ry, &mut visited, m);
63 |
64 | for y in 0..ROWS {
65 | for x in 0..COLS {
66 | if visited[y as usize][x as usize] == 1 {
67 | m[y as usize][x as usize] = TileType::Floor as i32;
68 | } else {
69 | m[y as usize][x as usize] = TileType::Wall as i32;
70 | }
71 | }
72 | }
73 |
74 | let num_floor: i32 = visited
75 | .iter()
76 | .flat_map(|x: &[i32; COLS as usize]| x.iter())
77 | .sum();
78 |
79 | num_floor
80 | }
81 |
82 | fn dfs(x: i32, y: i32, v: &mut Map, m: &Map) {
83 | v[y as usize][x as usize] = TileType::Floor as i32;
84 | if is_valid(x - 1, y, v, m) {
85 | dfs(x - 1, y, v, m);
86 | }
87 | if is_valid(x + 1, y, v, m) {
88 | dfs(x + 1, y, v, m);
89 | }
90 | if is_valid(x, y - 1, v, m) {
91 | dfs(x, y - 1, v, m);
92 | }
93 | if is_valid(x, y + 1, v, m) {
94 | dfs(x, y + 1, v, m);
95 | }
96 | }
97 |
98 | fn is_valid(x: i32, y: i32, v: &Map, m: &Map) -> bool {
99 | if (x < 0) | (x >= COLS) | (y < 0) | (y >= ROWS) {
100 | return false;
101 | }
102 | if v[y as usize][x as usize] == TileType::Floor as i32 {
103 | return false;
104 | }
105 | if m[y as usize][x as usize] == TileType::Wall as i32 {
106 | return false;
107 | }
108 | true
109 | }
110 |
111 | fn generate_caves(m: &mut Map) {
112 | randomize_map_seal_edges(m);
113 | for _ in 0..15 {
114 | evolve_map(m);
115 | }
116 | }
117 |
118 | pub struct CellularAutomataGenerator {}
119 | impl CellularAutomataGenerator {
120 | pub fn generate_map() -> Map {
121 | let mut map = new_map(TileType::Wall);
122 | generate_caves(&mut map);
123 | while get_random_cave_size(&mut map) < 1000 {
124 | generate_caves(&mut map);
125 | }
126 | map
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/procgen/maze_with_rooms.rs:
--------------------------------------------------------------------------------
1 | use crate::{fundamentals::*, maptools::*, utils::*};
2 |
3 | #[derive(Copy, Clone, Debug)]
4 | struct Cell {
5 | x: i32,
6 | y: i32,
7 | }
8 |
9 | impl Cell {
10 | fn new(x: i32, y: i32, visited: &mut Map, visited_positions: &mut Vec) -> Self {
11 | visited[y as usize][x as usize] = TileType::Floor as i32;
12 | visited_positions.push(Pos { x, y });
13 | Self { x, y }
14 | }
15 |
16 | fn step(&mut self, m: &mut Map, v: &mut Map, visited_positions: &mut Vec) {
17 | let neighbors = get_neighbors(self.x, self.y, m);
18 | let mut valid_neighbors: Vec = Vec::new();
19 | if let Some(n) = neighbors.north {
20 | valid_neighbors.push(n);
21 | }
22 | if let Some(e) = neighbors.east {
23 | valid_neighbors.push(e);
24 | }
25 | if let Some(s) = neighbors.south {
26 | valid_neighbors.push(s);
27 | }
28 | if let Some(w) = neighbors.west {
29 | valid_neighbors.push(w);
30 | }
31 | // backtrack if no valid neighbors
32 | if valid_neighbors.is_empty() {
33 | if visited_positions.is_empty() {
34 | return;
35 | }
36 | let prev_loc = visited_positions.pop().unwrap();
37 | (self.x, self.y) = (prev_loc.x, prev_loc.y);
38 | return;
39 | }
40 |
41 | let next = valid_neighbors[randr(0..valid_neighbors.len() as i32) as usize];
42 | (self.x, self.y) = (next.x, next.y);
43 | v[self.y as usize][self.x as usize] = TileType::Floor as i32;
44 | visited_positions.push(Pos {
45 | x: self.x,
46 | y: self.y,
47 | });
48 | m[self.y as usize][self.x as usize] = TileType::Floor as i32;
49 | }
50 | }
51 |
52 | struct Neighbors {
53 | north: Option,
54 | east: Option,
55 | south: Option,
56 | west: Option,
57 | }
58 |
59 | fn can_grow_tunnel(x: i32, y: i32, v: &Map) -> bool {
60 | if in_bounds(x, y) && v[y as usize][x as usize] == TileType::Wall as i32 {
61 | return true;
62 | }
63 | false
64 | }
65 |
66 | fn get_neighbors(x: i32, y: i32, m: &Map) -> Neighbors {
67 | let mut n: Option = None;
68 | let mut e: Option = None;
69 | let mut s: Option = None;
70 | let mut w: Option = None;
71 |
72 | if can_grow_tunnel(x, y - 1, m)
73 | && can_grow_tunnel(x, y - 2, m)
74 | && can_grow_tunnel(x - 1, y - 1, m)
75 | && can_grow_tunnel(x - 1, y - 2, m)
76 | && can_grow_tunnel(x + 1, y - 1, m)
77 | && can_grow_tunnel(x + 1, y - 2, m)
78 | {
79 | n = Some(Cell { x, y: y - 1 });
80 | }
81 | if can_grow_tunnel(x + 1, y, m)
82 | && can_grow_tunnel(x + 2, y, m)
83 | && can_grow_tunnel(x + 1, y - 1, m)
84 | && can_grow_tunnel(x + 2, y - 1, m)
85 | && can_grow_tunnel(x + 1, y + 1, m)
86 | && can_grow_tunnel(x + 2, y + 1, m)
87 | {
88 | e = Some(Cell { x: x + 1, y });
89 | }
90 | if can_grow_tunnel(x, y + 1, m)
91 | && can_grow_tunnel(x, y + 2, m)
92 | && can_grow_tunnel(x - 1, y + 1, m)
93 | && can_grow_tunnel(x - 1, y + 2, m)
94 | && can_grow_tunnel(x + 1, y + 1, m)
95 | && can_grow_tunnel(x + 1, y + 2, m)
96 | {
97 | s = Some(Cell { x, y: y + 1 });
98 | }
99 | if can_grow_tunnel(x - 1, y, m)
100 | && can_grow_tunnel(x - 2, y, m)
101 | && can_grow_tunnel(x - 2, y - 1, m)
102 | && can_grow_tunnel(x - 1, y - 1, m)
103 | && can_grow_tunnel(x - 2, y + 1, m)
104 | && can_grow_tunnel(x - 1, y + 1, m)
105 | {
106 | w = Some(Cell { x: x - 1, y });
107 | }
108 |
109 | Neighbors {
110 | north: n,
111 | east: e,
112 | south: s,
113 | west: w,
114 | }
115 | }
116 |
117 | fn count_neighbors(p: &Pos, v: &Map) -> i32 {
118 | let mut count = 0;
119 | if in_bounds(p.x - 1, p.y) && v[p.y as usize][p.x as usize - 1] == TileType::Floor as i32 {
120 | count = count + 1;
121 | }
122 | if in_bounds(p.x + 1, p.y) && v[p.y as usize][p.x as usize + 1] == TileType::Floor as i32 {
123 | count = count + 1;
124 | }
125 | if in_bounds(p.x, p.y - 1) && v[p.y as usize - 1][p.x as usize] == TileType::Floor as i32 {
126 | count = count + 1;
127 | }
128 | if in_bounds(p.x, p.y + 1) && v[p.y as usize + 1][p.x as usize] == TileType::Floor as i32 {
129 | count = count + 1;
130 | }
131 | count
132 | }
133 |
134 | fn trim_dead_ends(m: &mut Map) {
135 | // find dead ends - cells with exactly one neighbor
136 | let dead_ends: Vec = m
137 | .iter()
138 | .flat_map(|x: &[i32; COLS as usize]| x.iter())
139 | .enumerate()
140 | .filter(|(_, &d)| d == 1)
141 | .map(|(idx, _)| {
142 | let x = idx as i32 % COLS;
143 | let y = idx as i32 / COLS;
144 | Pos { x, y }
145 | })
146 | .filter(|p| count_neighbors(p, m) == 1)
147 | .collect();
148 | for d in dead_ends {
149 | m[d.y as usize][d.x as usize] = TileType::Wall as i32;
150 | }
151 | }
152 |
153 | fn place_rooms(m: &mut Map) -> Vec {
154 | const ROOM_SIZE_MIN: i32 = 6;
155 | const ROOMS_SIZE_MAX: i32 = 16;
156 | let mut rooms: Vec = vec![];
157 |
158 | for _ in 0..20 {
159 | let w: i32 = randr(ROOM_SIZE_MIN..ROOMS_SIZE_MAX);
160 | let h: i32 = randr(ROOM_SIZE_MIN..ROOMS_SIZE_MAX);
161 | let x: i32 = randr(1..COLS - w);
162 | let y: i32 = randr(1..ROWS - h);
163 |
164 | let mut curr_room = Room::new(x, y, w, h);
165 |
166 | let mut overlaps = false;
167 |
168 | for room in &rooms {
169 | if curr_room.overlaps(*room) {
170 | overlaps = true;
171 | break;
172 | }
173 | }
174 |
175 | if !overlaps {
176 | // increase room rect size to allow for sparser room placement
177 | curr_room.carve(m);
178 | curr_room.x1 = curr_room.x1 - 3;
179 | curr_room.y1 = curr_room.y1 - 3;
180 | curr_room.x2 = curr_room.x2 + 3;
181 | curr_room.y2 = curr_room.y2 + 3;
182 | rooms.push(curr_room);
183 | }
184 | }
185 |
186 | rooms
187 | }
188 |
189 | fn connect_rooms(rooms: &mut Vec, m: &mut Map) {
190 | for room in rooms {
191 | // find possible connection points
192 | // = walls of the room which have a tunnel next to them
193 |
194 | // first shrink the room rect after placement
195 | room.x1 = room.x1 + 3;
196 | room.x2 = room.x2 - 3;
197 | room.y1 = room.y1 + 3;
198 | room.y2 = room.y2 - 3;
199 |
200 | // find the room walls
201 | let mut perimeter: Vec = Vec::new();
202 | for x in room.x1..room.x2 {
203 | for y in room.y1..room.y2 {
204 | if x == room.x1 || x == room.x2 - 1 || y == room.y1 || y == room.y2 - 1 {
205 | perimeter.push(Pos { x, y });
206 | }
207 | }
208 | }
209 |
210 | let mut possible_connection_points: Vec = Vec::new();
211 | for p in perimeter {
212 | if in_bounds(p.x - 1, p.y)
213 | && in_bounds(p.x - 2, p.y)
214 | && m[p.y as usize][p.x as usize - 1] == TileType::Wall as i32
215 | && m[p.y as usize][p.x as usize - 2] == TileType::Floor as i32
216 | {
217 | possible_connection_points.push(p);
218 | }
219 | if in_bounds(p.x + 1, p.y)
220 | && in_bounds(p.x + 2, p.y)
221 | && m[p.y as usize][p.x as usize + 1] == TileType::Wall as i32
222 | && m[p.y as usize][p.x as usize + 2] == TileType::Floor as i32
223 | {
224 | possible_connection_points.push(p);
225 | }
226 | if in_bounds(p.x, p.y - 1)
227 | && in_bounds(p.x, p.y - 2)
228 | && m[p.y as usize - 1][p.x as usize] == TileType::Wall as i32
229 | && m[p.y as usize - 2][p.x as usize] == TileType::Floor as i32
230 | {
231 | possible_connection_points.push(p);
232 | }
233 | if in_bounds(p.x, p.y + 1)
234 | && in_bounds(p.x, p.y + 2)
235 | && m[p.y as usize + 1][p.x as usize] == TileType::Wall as i32
236 | && m[p.y as usize + 2][p.x as usize] == TileType::Floor as i32
237 | {
238 | possible_connection_points.push(p);
239 | }
240 | }
241 |
242 | if possible_connection_points.is_empty() {
243 | return;
244 | }
245 |
246 | let mut connection_points: Vec = Vec::new();
247 | for _ in 0..2 {
248 | let connection_point = possible_connection_points
249 | [randr(0..possible_connection_points.len() as i32) as usize];
250 | connection_points.push(connection_point);
251 | }
252 |
253 | for p in connection_points {
254 | if in_bounds(p.x - 1, p.y)
255 | && in_bounds(p.x - 2, p.y)
256 | && m[p.y as usize][p.x as usize - 1] == TileType::Wall as i32
257 | && m[p.y as usize][p.x as usize - 2] == TileType::Floor as i32
258 | {
259 | m[p.y as usize][p.x as usize - 1] = TileType::Floor as i32;
260 | continue;
261 | }
262 | if in_bounds(p.x + 1, p.y)
263 | && in_bounds(p.x + 2, p.y)
264 | && m[p.y as usize][p.x as usize + 1] == TileType::Wall as i32
265 | && m[p.y as usize][p.x as usize + 2] == TileType::Floor as i32
266 | {
267 | m[p.y as usize][p.x as usize + 1] = TileType::Floor as i32;
268 | continue;
269 | }
270 | if in_bounds(p.x, p.y - 1)
271 | && in_bounds(p.x, p.y - 2)
272 | && m[p.y as usize - 1][p.x as usize] == TileType::Wall as i32
273 | && m[p.y as usize - 2][p.x as usize] == TileType::Floor as i32
274 | {
275 | m[p.y as usize - 1][p.x as usize] = TileType::Floor as i32;
276 | continue;
277 | }
278 | if in_bounds(p.x, p.y + 1)
279 | && in_bounds(p.x, p.y + 2)
280 | && m[p.y as usize + 1][p.x as usize] == TileType::Wall as i32
281 | && m[p.y as usize + 2][p.x as usize] == TileType::Floor as i32
282 | {
283 | m[p.y as usize + 1][p.x as usize] = TileType::Floor as i32;
284 | continue;
285 | }
286 | }
287 | }
288 | }
289 |
290 | #[derive(Copy, Clone, Debug)]
291 | struct Pos {
292 | x: i32,
293 | y: i32,
294 | }
295 |
296 | pub struct MazeGenerator {}
297 | impl MazeGenerator {
298 | pub fn generate_map() -> Map {
299 | let mut map = new_map(TileType::Wall);
300 | let mut visited = new_map(TileType::Wall);
301 | let mut visited_positions: Vec = Vec::new();
302 |
303 | let mut rooms = place_rooms(&mut map);
304 |
305 | // pick a random wall location
306 | let mut startx = COLS / 2;
307 | let mut starty = ROWS / 2;
308 | while map[starty as usize][startx as usize] != TileType::Wall as i32 {
309 | startx = randr(0..COLS);
310 | starty = randr(0..ROWS);
311 | }
312 |
313 | let mut c = Cell::new(startx, starty, &mut visited, &mut visited_positions);
314 |
315 | while !visited_positions.is_empty() {
316 | c.step(&mut map, &mut visited, &mut visited_positions);
317 | }
318 |
319 | for y in 0..ROWS {
320 | for x in 0..COLS {
321 | if visited[y as usize][x as usize] == TileType::Floor as i32 {
322 | map[y as usize][x as usize] = TileType::Floor as i32;
323 | }
324 | }
325 | }
326 |
327 | // make maze passages sparser by trimming some dead ends
328 | for _ in 0..5 {
329 | trim_dead_ends(&mut map);
330 | }
331 |
332 | // connect rooms with passages
333 | connect_rooms(&mut rooms, &mut map);
334 |
335 | map
336 | }
337 | }
338 |
--------------------------------------------------------------------------------
/src/procgen/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod bsp_tree;
2 | pub mod cellular_automata;
3 | pub mod maze_with_rooms;
4 | pub mod room_placement;
5 | pub mod rwalk;
6 | pub mod tunneling;
7 |
--------------------------------------------------------------------------------
/src/procgen/room_placement.rs:
--------------------------------------------------------------------------------
1 | use crate::{fundamentals::*, maptools::*, utils::*};
2 |
3 | const SQUARE_ROOM_MIN_SIZE: i32 = 4;
4 | const SQUARE_ROOM_MAX_SIZE: i32 = 8;
5 | const CROSS_ROOM_MIN_SIZE: i32 = 6;
6 | const CROSS_ROOM_MAX_SIZE: i32 = 16;
7 | const CAVE_ROOM_MIN_SIZE: i32 = 8;
8 | const CAVE_ROOM_MAX_SIZE: i32 = 14;
9 |
10 | #[derive(Debug)]
11 | struct Room {
12 | rect: Rect,
13 | tiles: Vec,
14 | }
15 |
16 | impl Room {
17 | fn center(&self) -> (i32, i32) {
18 | (self.rect.w / 2, self.rect.h / 2)
19 | }
20 | }
21 |
22 | fn room_get_xy(x: i32, y: i32, cols: i32) -> i32 {
23 | y * cols + x
24 | }
25 |
26 | fn room_get_coord_from_idx(idx: i32, cols: i32) -> (i32, i32) {
27 | let x = idx % cols;
28 | let y = idx / cols;
29 | (x, y)
30 | }
31 |
32 | #[derive(Copy, Clone, Debug, PartialEq)]
33 | struct TileWithXYIdx {
34 | tile: TileType,
35 | x: i32,
36 | y: i32,
37 | idx: i32,
38 | }
39 |
40 | #[derive(Copy, Clone, Debug, PartialEq)]
41 | struct ConnectionPoint {
42 | tile: TileWithXYIdx,
43 | loc: ConnectionPointLocation,
44 | }
45 |
46 | #[derive(Copy, Clone, Debug, PartialEq)]
47 | enum ConnectionPointLocation {
48 | Top,
49 | Right,
50 | Bottom,
51 | Left,
52 | }
53 |
54 | fn find_connection_points(r: &Room) -> Vec {
55 | // get all (x,y) coords for all tiles
56 | let tiles_with_idx: Vec = r
57 | .tiles
58 | .iter()
59 | .enumerate()
60 | .map(|(i, t)| {
61 | let (x, y) = room_get_coord_from_idx(i as i32, r.rect.w);
62 | return TileWithXYIdx {
63 | tile: *t,
64 | idx: i as i32,
65 | x,
66 | y,
67 | };
68 | })
69 | .collect();
70 |
71 | // find all floors, work only with those further
72 | let floor_tiles: Vec = tiles_with_idx
73 | .into_iter()
74 | .filter(|&t| t.tile as i32 == TileType::Floor as i32)
75 | .collect();
76 |
77 | // find boundaries
78 | let min_x = floor_tiles.iter().min_by_key(|&t| t.x).unwrap().x;
79 | let max_x = floor_tiles.iter().max_by_key(|&t| t.x).unwrap().x;
80 | let min_y = floor_tiles.iter().min_by_key(|&t| t.y).unwrap().y;
81 | let max_y = floor_tiles.iter().max_by_key(|&t| t.y).unwrap().y;
82 |
83 | let mut boundaries: Vec> = Vec::new();
84 | boundaries.push(floor_tiles.iter().filter(|&t| t.x == min_x).collect());
85 | boundaries.push(floor_tiles.iter().filter(|&t| t.x == max_x).collect());
86 | boundaries.push(floor_tiles.iter().filter(|&t| t.y == min_y).collect());
87 | boundaries.push(floor_tiles.iter().filter(|&t| t.y == max_y).collect());
88 |
89 | // select random point from boundary points
90 | let mut connection_points: Vec = Vec::new();
91 | for (i, b) in boundaries.iter().enumerate() {
92 | let connection_idx = if b.len() <= 2 {
93 | randr(0..b.len() as i32)
94 | } else {
95 | randr(1..b.len() as i32 - 1)
96 | };
97 |
98 | let connection_point = b[connection_idx as usize];
99 | connection_points.push(ConnectionPoint {
100 | tile: *connection_point,
101 | loc: match i {
102 | 0 => ConnectionPointLocation::Left,
103 | 1 => ConnectionPointLocation::Right,
104 | 2 => ConnectionPointLocation::Top,
105 | _ => ConnectionPointLocation::Bottom,
106 | },
107 | });
108 | }
109 |
110 | for c in &mut connection_points {
111 | c.tile.x = c.tile.x + r.rect.x;
112 | c.tile.y = c.tile.y + r.rect.y;
113 | c.tile.idx = get_xy_idx(c.tile.x, c.tile.y);
114 | }
115 |
116 | connection_points
117 | }
118 |
119 | fn get_xy_idx(x: i32, y: i32) -> i32 {
120 | y * COLS + x
121 | }
122 |
123 | fn generate_square_room(size_min: i32, size_max: i32) -> Room {
124 | let room_size = randr(size_min..size_max + 1);
125 | let mut tiles: Vec = vec![TileType::Wall; (room_size * room_size) as usize];
126 | let rect = Rect {
127 | x: 0,
128 | y: 0,
129 | w: room_size,
130 | h: room_size,
131 | };
132 | for x in 0..room_size {
133 | for y in 0..room_size {
134 | tiles[room_get_xy(x, y, room_size) as usize] = TileType::Floor;
135 | }
136 | }
137 |
138 | Room { rect, tiles }
139 | }
140 |
141 | fn generate_rectangular_room(size_min: i32, size_max: i32) -> Room {
142 | let room_width = randr(size_min..size_max + 1);
143 | let room_height = randr(size_min..size_max + 1);
144 | let mut tiles: Vec = vec![TileType::Wall; (room_width * room_height) as usize];
145 | let rect = Rect {
146 | x: 0,
147 | y: 0,
148 | w: room_width,
149 | h: room_height,
150 | };
151 | for x in 0..room_width {
152 | for y in 0..room_height {
153 | tiles[room_get_xy(x, y, room_width) as usize] = TileType::Floor;
154 | }
155 | }
156 | Room { rect, tiles }
157 | }
158 |
159 | fn generate_cross_room(size_min: i32, size_max: i32) -> Room {
160 | let mut r = generate_rectangular_room(size_min, size_max);
161 | let w_third = r.rect.w / 3;
162 | let h_third = r.rect.h / 3;
163 | for x in 0..w_third {
164 | for y in 0..h_third {
165 | r.tiles[room_get_xy(x, y, r.rect.w) as usize] = TileType::Wall;
166 | }
167 | for y in r.rect.h - h_third..r.rect.h {
168 | r.tiles[room_get_xy(x, y, r.rect.w) as usize] = TileType::Wall;
169 | }
170 | }
171 | for x in r.rect.w - w_third..r.rect.w {
172 | for y in 0..h_third {
173 | r.tiles[room_get_xy(x, y, r.rect.w) as usize] = TileType::Wall;
174 | }
175 | for y in r.rect.h - h_third..r.rect.h {
176 | r.tiles[room_get_xy(x, y, r.rect.w) as usize] = TileType::Wall;
177 | }
178 | }
179 | r
180 | }
181 |
182 | fn generate_circular_room(size_min: i32, size_max: i32) -> Room {
183 | let mut r = generate_square_room(size_min, size_max);
184 | let radius = r.rect.w / 2;
185 | let (cx, cy) = r.center();
186 | for x in 0..r.rect.w {
187 | for y in 0..r.rect.h {
188 | let dist = (((x - cx).pow(2) + (y - cy).pow(2)) as f64).sqrt();
189 | if dist.round() >= radius as f64 {
190 | r.tiles[room_get_xy(x, y, r.rect.w) as usize] = TileType::Wall;
191 | }
192 | }
193 | }
194 | r
195 | }
196 |
197 | fn generate_cave_room(size_min: i32, size_max: i32) -> Room {
198 | let mut r = generate_square_room(size_min, size_max);
199 |
200 | generate_cave(&mut r);
201 | while get_random_cave_size(&mut r) < 80 {
202 | r = generate_square_room(size_min, size_max);
203 | generate_cave(&mut r);
204 | }
205 | r
206 | }
207 |
208 | fn generate_cave(r: &mut Room) {
209 | for x in 0..r.rect.w {
210 | for y in 0..r.rect.h {
211 | if x == 0 || x == r.rect.w || y == 0 || y == r.rect.h {
212 | r.tiles[room_get_xy(x, y, r.rect.w) as usize] = TileType::Wall;
213 | } else {
214 | if randr(0..100) > 70 {
215 | r.tiles[room_get_xy(x, y, r.rect.w) as usize] = TileType::Wall;
216 | }
217 | }
218 | }
219 | }
220 | for _ in 0..5 {
221 | evolve_map(r);
222 | }
223 | }
224 |
225 | fn evolve_map(r: &mut Room) {
226 | let death_limit = 3;
227 | let birth_limit = 4;
228 |
229 | for y in 0..r.rect.w {
230 | for x in 0..r.rect.h {
231 | let neighbor_count = count_alive_neighbors(r, x, y);
232 | let tile = &mut r.tiles[room_get_xy(x, y, r.rect.w) as usize];
233 | if *tile as i32 == TileType::Wall as i32 {
234 | if neighbor_count < death_limit {
235 | *tile = TileType::Floor;
236 | }
237 | } else {
238 | if birth_limit < neighbor_count {
239 | *tile = TileType::Wall;
240 | }
241 | }
242 | }
243 | }
244 | }
245 |
246 | fn count_alive_neighbors(r: &Room, x: i32, y: i32) -> i32 {
247 | let mut count = 0;
248 | for i in -1..2 {
249 | for j in -1..2 {
250 | let nx = x + i;
251 | let ny = y + j;
252 | if (nx < 0) | (nx >= r.rect.w) | (ny < 0) | (ny >= r.rect.h) {
253 | count += 1;
254 | } else if r.tiles[room_get_xy(nx, ny, r.rect.w) as usize] as i32
255 | == TileType::Wall as i32
256 | {
257 | count += 1;
258 | }
259 | }
260 | }
261 | count
262 | }
263 |
264 | fn get_random_cave_size(r: &mut Room) -> i32 {
265 | // count as 0 if less than 10 floors
266 | let mut num_floor: i32 = r.tiles.iter().map(|x| (*x) as i32).sum();
267 | if num_floor < 10 {
268 | return 0;
269 | }
270 |
271 | // get random floor starting position for DFS
272 | let mut rx = randr(0..r.rect.w);
273 | let mut ry = randr(0..r.rect.h);
274 | while r.tiles[room_get_xy(rx, ry, r.rect.w) as usize] as i32 != TileType::Floor as i32 {
275 | rx = randr(0..r.rect.w);
276 | ry = randr(0..r.rect.h);
277 | }
278 | let mut visited: Vec = Vec::with_capacity((r.rect.w * r.rect.h) as usize);
279 | for _ in r.tiles.iter() {
280 | visited.push(0);
281 | }
282 | dfs(rx, ry, &mut visited, r);
283 |
284 | for (pos, tile) in r.tiles.iter_mut().enumerate() {
285 | if visited[pos] == 1 {
286 | *tile = TileType::Floor;
287 | } else {
288 | *tile = TileType::Wall;
289 | }
290 | }
291 |
292 | num_floor = visited.iter().sum();
293 | num_floor
294 | }
295 |
296 | fn dfs(x: i32, y: i32, v: &mut [i32], r: &Room) {
297 | v[room_get_xy(x, y, r.rect.w) as usize] = 1;
298 | if is_valid(x - 1, y, v, r) {
299 | dfs(x - 1, y, v, r);
300 | }
301 | if is_valid(x + 1, y, v, r) {
302 | dfs(x + 1, y, v, r);
303 | }
304 | if is_valid(x, y - 1, v, r) {
305 | dfs(x, y - 1, v, r);
306 | }
307 | if is_valid(x, y + 1, v, r) {
308 | dfs(x, y + 1, v, r);
309 | }
310 | }
311 |
312 | fn is_valid(x: i32, y: i32, v: &[i32], r: &Room) -> bool {
313 | if (x < 0) | (x >= r.rect.w) | (y < 0) | (y >= r.rect.h) {
314 | return false;
315 | }
316 | if v[room_get_xy(x, y, r.rect.w) as usize] == 1 {
317 | return false;
318 | }
319 | if r.tiles[room_get_xy(x, y, r.rect.w) as usize] as i32 == TileType::Wall as i32 {
320 | return false;
321 | }
322 |
323 | true
324 | }
325 |
326 | /// place_room transposes the Room rect to map coordinates, and carves the Room tiles
327 | fn place_room(r: &mut Room, m: &mut Map, xoff: i32, yoff: i32) -> bool {
328 | r.rect.x = xoff;
329 | r.rect.y = yoff;
330 | // first pass, check if new room is in bounds & no overlap
331 | for x in xoff - 1..xoff + r.rect.w + 1 {
332 | for y in yoff - 1..yoff + r.rect.h + 1 {
333 | if !in_bounds(x, y) {
334 | return false;
335 | }
336 | if m[y as usize][x as usize] == TileType::Floor as i32 {
337 | return false;
338 | }
339 | }
340 | }
341 | // second pass, place room if first pass was OK
342 | for x in xoff..xoff + r.rect.w {
343 | for y in yoff..yoff + r.rect.h {
344 | m[y as usize][x as usize] =
345 | r.tiles[room_get_xy(x - xoff, y - yoff, r.rect.w) as usize] as i32;
346 | }
347 | }
348 | true
349 | }
350 |
351 | const N_ROOM_TYPE: i32 = 5;
352 | enum RoomType {
353 | Square,
354 | Rectangle,
355 | Cross,
356 | Circle,
357 | Cave,
358 | }
359 |
360 | fn generate_random_room() -> Room {
361 | let room_type: RoomType = match randr(0..N_ROOM_TYPE) {
362 | 0 => RoomType::Square,
363 | 1 => RoomType::Rectangle,
364 | 2 => RoomType::Cross,
365 | 3 => RoomType::Circle,
366 | _ => RoomType::Cave,
367 | };
368 | let r = match room_type {
369 | RoomType::Square => generate_square_room(SQUARE_ROOM_MIN_SIZE, SQUARE_ROOM_MAX_SIZE),
370 | RoomType::Rectangle => {
371 | generate_rectangular_room(SQUARE_ROOM_MIN_SIZE, SQUARE_ROOM_MAX_SIZE)
372 | }
373 | RoomType::Cross => generate_cross_room(CROSS_ROOM_MIN_SIZE, CROSS_ROOM_MAX_SIZE),
374 | RoomType::Circle => generate_circular_room(SQUARE_ROOM_MIN_SIZE, SQUARE_ROOM_MAX_SIZE),
375 | RoomType::Cave => generate_cave_room(CAVE_ROOM_MIN_SIZE, CAVE_ROOM_MAX_SIZE),
376 | };
377 | r
378 | }
379 |
380 | fn try_place_room(free_connection_points: &mut Vec, map: &mut Map) -> bool {
381 | // generate a random type room
382 | let mut r = generate_random_room();
383 |
384 | // select random connection point on starting room
385 | let cp = free_connection_points[randr(0..free_connection_points.len() as i32) as usize];
386 |
387 | // try to move room next to starting room connection point, depending on cp location
388 | let placement_offset = 4;
389 | let (tx, ty) = match cp.loc {
390 | ConnectionPointLocation::Top => (
391 | cp.tile.x - r.rect.w / 2,
392 | cp.tile.y - r.rect.h - placement_offset,
393 | ),
394 | ConnectionPointLocation::Right => (cp.tile.x + placement_offset, cp.tile.y - r.rect.h / 2),
395 | ConnectionPointLocation::Bottom => (cp.tile.x - r.rect.w / 2, cp.tile.y + placement_offset),
396 | ConnectionPointLocation::Left => (
397 | cp.tile.x - r.rect.w - placement_offset,
398 | cp.tile.y - r.rect.h / 2,
399 | ),
400 | };
401 |
402 | if !place_room(&mut r, map, tx, ty) {
403 | return false;
404 | }
405 |
406 | let mut new_room_connection_points = find_connection_points(&r);
407 |
408 | match cp.loc {
409 | ConnectionPointLocation::Top => {
410 | new_room_connection_points.retain(|&p| p.loc != ConnectionPointLocation::Bottom)
411 | }
412 | ConnectionPointLocation::Right => {
413 | new_room_connection_points.retain(|&p| p.loc != ConnectionPointLocation::Left)
414 | }
415 | ConnectionPointLocation::Bottom => {
416 | new_room_connection_points.retain(|&p| p.loc != ConnectionPointLocation::Top)
417 | }
418 | ConnectionPointLocation::Left => {
419 | new_room_connection_points.retain(|&p| p.loc != ConnectionPointLocation::Right)
420 | }
421 | }
422 |
423 | // connect rooms
424 | match cp.loc {
425 | ConnectionPointLocation::Top => {
426 | let tx = cp.tile.x;
427 | let mut ty = cp.tile.y;
428 | while map[ty as usize - 1][tx as usize] != TileType::Floor as i32 {
429 | map[ty as usize - 1][tx as usize] = TileType::Floor as i32;
430 | ty = ty - 1;
431 | }
432 | }
433 | ConnectionPointLocation::Bottom => {
434 | let tx = cp.tile.x;
435 | let mut ty = cp.tile.y;
436 | while map[ty as usize + 1][tx as usize] != TileType::Floor as i32 {
437 | map[ty as usize + 1][tx as usize] = TileType::Floor as i32;
438 | ty = ty + 1;
439 | }
440 | }
441 | ConnectionPointLocation::Left => {
442 | let mut tx = cp.tile.x;
443 | let ty = cp.tile.y;
444 | while map[ty as usize][tx as usize - 1] != TileType::Floor as i32 {
445 | map[ty as usize][tx as usize - 1] = TileType::Floor as i32;
446 | tx = tx - 1;
447 | }
448 | }
449 | ConnectionPointLocation::Right => {
450 | let mut tx = cp.tile.x;
451 | let ty = cp.tile.y;
452 | while map[ty as usize][tx as usize + 1] != TileType::Floor as i32 {
453 | map[ty as usize][tx as usize + 1] = TileType::Floor as i32;
454 | tx = tx + 1;
455 | }
456 | }
457 | }
458 |
459 | // remove used up connection point on previous room
460 | free_connection_points.retain(|&p| (p != cp));
461 | // add new room's free connection points
462 | free_connection_points.extend(new_room_connection_points);
463 |
464 | true
465 | }
466 |
467 | pub struct RoomPlacementGenerator {}
468 |
469 | impl RoomPlacementGenerator {
470 | pub fn generate_map() -> Map {
471 | let mut map = new_map(TileType::Wall);
472 |
473 | // generate & place starting room in center
474 | let mut r1 = generate_random_room();
475 | let starting_room_x = COLS / 2 - r1.rect.w / 2;
476 | let starting_room_y = ROWS / 2 - r1.rect.h / 2;
477 | place_room(&mut r1, &mut map, starting_room_x, starting_room_y);
478 |
479 | // add starting room's connection points to vec containg all free connection points
480 | let mut free_connection_points = find_connection_points(&r1);
481 |
482 | let max_attempts = 100;
483 | let mut rooms_placed = 1;
484 | for _ in 0..max_attempts {
485 | if try_place_room(&mut free_connection_points, &mut map) {
486 | rooms_placed = rooms_placed + 1;
487 | }
488 | }
489 |
490 | // println!(
491 | // "Placed {} rooms out of {} attempts.",
492 | // rooms_placed, max_attempts
493 | // );
494 |
495 | // for c in free_connection_points {
496 | // map[get_xy_idx(c.tile.x, c.tile.y) as usize] = TileType::Hero as i32;
497 | // }
498 |
499 | map
500 | }
501 | }
502 |
--------------------------------------------------------------------------------
/src/procgen/rwalk.rs:
--------------------------------------------------------------------------------
1 | use crate::{fundamentals::*, maptools::*, utils::*};
2 |
3 | const MAX_WALKERS: i32 = 10;
4 | const MAX_STEPS: i32 = 200;
5 |
6 | #[derive(Copy, Clone, Debug)]
7 | struct Walker {
8 | x: i32,
9 | y: i32,
10 | pub steps: i32,
11 | }
12 |
13 | impl Walker {
14 | fn new(x: i32, y: i32) -> Self {
15 | Self {
16 | x,
17 | y,
18 | steps: MAX_STEPS,
19 | }
20 | }
21 |
22 | fn step(&mut self, map: &mut Map) {
23 | if self.steps <= 0 {
24 | return;
25 | }
26 |
27 | let direction = randr(0..4);
28 | let (mut dx, mut dy) = (0, 0);
29 | match direction {
30 | 0 => dx = 1,
31 | 1 => dx = -1,
32 | 2 => dy = 1,
33 | _ => dy = -1,
34 | }
35 |
36 | let tx = self.x + dx;
37 | let ty = self.y + dy;
38 |
39 | if tx < 1 || tx >= COLS - 1 || ty < 1 || ty >= ROWS - 1 {
40 | return;
41 | }
42 |
43 | self.x = tx;
44 | self.y = ty;
45 | self.steps -= 1;
46 | map[self.y as usize][self.x as usize] = TileType::Floor as i32;
47 | }
48 | }
49 |
50 | fn spawn_walker<'a>(x: i32, y: i32, vec: &'a mut Vec, map: &mut Map) {
51 | vec.push(Walker::new(x, y));
52 | map[y as usize][x as usize] = TileType::Floor as i32;
53 | }
54 |
55 | pub struct RandomWalkGenerator {}
56 | impl RandomWalkGenerator {
57 | pub fn generate_map() -> Map {
58 | let mut map = new_map(TileType::Wall);
59 | let mut walkers: Vec = Vec::new();
60 | let mut num_walkers = 0;
61 |
62 | spawn_walker(COLS / 2, ROWS / 2, &mut walkers, &mut map);
63 | num_walkers += 1;
64 |
65 | // until we have active walkers
66 | while walkers.len() > 0 {
67 | // create vector for possible newly spawned walkers in this iteration
68 | let mut walkers_spawned: Vec = Vec::new();
69 |
70 | // each walker takes step
71 | for w in &mut walkers {
72 | w.step(&mut map);
73 | // after each step, chance to spawn new walker at walker's current location (if we can)
74 | if (num_walkers < MAX_WALKERS) & (randr(0..100) > 80) {
75 | spawn_walker(w.x, w.y, &mut walkers_spawned, &mut map);
76 | num_walkers += 1;
77 | }
78 | }
79 |
80 | // if we did spawn new walker in this iteration, append it to the main walkers vector
81 | if walkers_spawned.len() > 0 {
82 | walkers.append(&mut walkers_spawned);
83 | }
84 |
85 | // keep only walkers that still have steps left in main walkers vector
86 | walkers.retain(|x| x.steps > 0);
87 | }
88 |
89 | map
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/procgen/tunneling.rs:
--------------------------------------------------------------------------------
1 | use crate::fundamentals::*;
2 | use crate::maptools::*;
3 | use crate::utils::*;
4 |
5 | pub struct TunnelingGenerator {}
6 | impl TunnelingGenerator {
7 | pub fn generate_map(room_size_min: i32, room_size_max: i32, max_rooms: i32) -> Map {
8 | let mut map = new_map(TileType::Wall);
9 | let mut rooms: Vec = vec![];
10 | let mut num_rooms = 0;
11 |
12 | for _ in 0..max_rooms {
13 | let w = randr(room_size_min..room_size_max);
14 | let h = randr(room_size_min..room_size_max);
15 | let x = randr(1..COLS - w);
16 | let y = randr(1..ROWS - h);
17 |
18 | let curr_room = Room::new(x, y, w, h);
19 | let mut overlaps = false;
20 |
21 | for room in &rooms {
22 | if curr_room.overlaps(*room) {
23 | overlaps = true;
24 | break;
25 | }
26 | }
27 |
28 | if !overlaps {
29 | curr_room.carve(&mut map);
30 |
31 | let (curr_x, curr_y) = curr_room.center();
32 |
33 | if num_rooms != 0 {
34 | let prev_room = rooms[num_rooms - 1];
35 | let (prev_x, prev_y) = prev_room.center();
36 |
37 | if randr(0..1) == 1 {
38 | carve_horz_tunnel(&mut map, curr_x, prev_x, curr_y);
39 | carve_vert_tunnel(&mut map, curr_y, prev_y, prev_x);
40 | } else {
41 | carve_vert_tunnel(&mut map, curr_y, prev_y, curr_x);
42 | carve_horz_tunnel(&mut map, curr_x, prev_x, prev_y);
43 | }
44 | }
45 |
46 | num_rooms += 1;
47 | rooms.push(curr_room);
48 | }
49 | }
50 | map
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/utils.rs:
--------------------------------------------------------------------------------
1 | use crate::fundamentals::{COLS, ROWS};
2 | use macroquad::rand;
3 | use std::ops::Range;
4 |
5 | pub fn randr(r: Range) -> i32 {
6 | rand::gen_range::(r.start, r.end)
7 | }
8 |
9 | pub fn in_bounds(x: i32, y: i32) -> bool {
10 | 0 <= x && x < COLS && 0 <= y && y < ROWS
11 | }
12 |
--------------------------------------------------------------------------------
| | | | | | | | |