├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── README.md ├── animations ├── ab.webp ├── dfs.webp ├── div.webp ├── prim.webp ├── tree.webp └── wilson.webp ├── rustfmt.toml └── src ├── main.rs └── map.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "ansi_term" 5 | version = "0.11.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | dependencies = [ 8 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 9 | ] 10 | 11 | [[package]] 12 | name = "arc-swap" 13 | version = "0.4.7" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | 16 | [[package]] 17 | name = "atty" 18 | version = "0.2.14" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | dependencies = [ 21 | "hermit-abi 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 22 | "libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)", 23 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 24 | ] 25 | 26 | [[package]] 27 | name = "bitflags" 28 | version = "1.2.1" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | 31 | [[package]] 32 | name = "cfg-if" 33 | version = "0.1.10" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | 36 | [[package]] 37 | name = "clap" 38 | version = "2.33.3" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | dependencies = [ 41 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 42 | "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", 43 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 44 | "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 45 | "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 46 | "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 47 | "vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", 48 | ] 49 | 50 | [[package]] 51 | name = "cloudabi" 52 | version = "0.0.3" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | dependencies = [ 55 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 56 | ] 57 | 58 | [[package]] 59 | name = "crossterm" 60 | version = "0.17.7" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | dependencies = [ 63 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 64 | "crossterm_winapi 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", 65 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 66 | "libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)", 67 | "mio 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 68 | "parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", 69 | "signal-hook 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 70 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 71 | ] 72 | 73 | [[package]] 74 | name = "crossterm_winapi" 75 | version = "0.6.1" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | dependencies = [ 78 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 79 | ] 80 | 81 | [[package]] 82 | name = "getrandom" 83 | version = "0.1.15" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | dependencies = [ 86 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 87 | "libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)", 88 | "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", 89 | ] 90 | 91 | [[package]] 92 | name = "hermit-abi" 93 | version = "0.1.16" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | dependencies = [ 96 | "libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)", 97 | ] 98 | 99 | [[package]] 100 | name = "lazy_static" 101 | version = "1.4.0" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | 104 | [[package]] 105 | name = "libc" 106 | version = "0.2.77" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | 109 | [[package]] 110 | name = "lock_api" 111 | version = "0.3.4" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | dependencies = [ 114 | "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 115 | ] 116 | 117 | [[package]] 118 | name = "log" 119 | version = "0.4.11" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | dependencies = [ 122 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 123 | ] 124 | 125 | [[package]] 126 | name = "maze_generator" 127 | version = "0.1.0" 128 | dependencies = [ 129 | "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", 130 | "crossterm 0.17.7 (registry+https://github.com/rust-lang/crates.io-index)", 131 | "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", 132 | ] 133 | 134 | [[package]] 135 | name = "mio" 136 | version = "0.7.0" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | dependencies = [ 139 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 140 | "libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)", 141 | "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", 142 | "miow 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 143 | "ntapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 144 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 145 | ] 146 | 147 | [[package]] 148 | name = "miow" 149 | version = "0.3.5" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | dependencies = [ 152 | "socket2 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 153 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 154 | ] 155 | 156 | [[package]] 157 | name = "ntapi" 158 | version = "0.3.4" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | dependencies = [ 161 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 162 | ] 163 | 164 | [[package]] 165 | name = "parking_lot" 166 | version = "0.10.2" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | dependencies = [ 169 | "lock_api 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 170 | "parking_lot_core 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 171 | ] 172 | 173 | [[package]] 174 | name = "parking_lot_core" 175 | version = "0.7.2" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | dependencies = [ 178 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 179 | "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 180 | "libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)", 181 | "redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)", 182 | "smallvec 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 183 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 184 | ] 185 | 186 | [[package]] 187 | name = "ppv-lite86" 188 | version = "0.2.9" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | 191 | [[package]] 192 | name = "rand" 193 | version = "0.7.3" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | dependencies = [ 196 | "getrandom 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", 197 | "libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)", 198 | "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 199 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 200 | "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 201 | ] 202 | 203 | [[package]] 204 | name = "rand_chacha" 205 | version = "0.2.2" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | dependencies = [ 208 | "ppv-lite86 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", 209 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 210 | ] 211 | 212 | [[package]] 213 | name = "rand_core" 214 | version = "0.5.1" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | dependencies = [ 217 | "getrandom 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", 218 | ] 219 | 220 | [[package]] 221 | name = "rand_hc" 222 | version = "0.2.0" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | dependencies = [ 225 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 226 | ] 227 | 228 | [[package]] 229 | name = "redox_syscall" 230 | version = "0.1.57" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | 233 | [[package]] 234 | name = "scopeguard" 235 | version = "1.1.0" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | 238 | [[package]] 239 | name = "signal-hook" 240 | version = "0.1.16" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | dependencies = [ 243 | "libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)", 244 | "mio 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 245 | "signal-hook-registry 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 246 | ] 247 | 248 | [[package]] 249 | name = "signal-hook-registry" 250 | version = "1.2.1" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | dependencies = [ 253 | "arc-swap 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", 254 | "libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)", 255 | ] 256 | 257 | [[package]] 258 | name = "smallvec" 259 | version = "1.4.2" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | 262 | [[package]] 263 | name = "socket2" 264 | version = "0.3.15" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | dependencies = [ 267 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 268 | "libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)", 269 | "redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)", 270 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 271 | ] 272 | 273 | [[package]] 274 | name = "strsim" 275 | version = "0.8.0" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | 278 | [[package]] 279 | name = "textwrap" 280 | version = "0.11.0" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | dependencies = [ 283 | "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 284 | ] 285 | 286 | [[package]] 287 | name = "unicode-width" 288 | version = "0.1.8" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | 291 | [[package]] 292 | name = "vec_map" 293 | version = "0.8.2" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | 296 | [[package]] 297 | name = "wasi" 298 | version = "0.9.0+wasi-snapshot-preview1" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | 301 | [[package]] 302 | name = "winapi" 303 | version = "0.3.9" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | dependencies = [ 306 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 307 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 308 | ] 309 | 310 | [[package]] 311 | name = "winapi-i686-pc-windows-gnu" 312 | version = "0.4.0" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | 315 | [[package]] 316 | name = "winapi-x86_64-pc-windows-gnu" 317 | version = "0.4.0" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | 320 | [metadata] 321 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 322 | "checksum arc-swap 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034" 323 | "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 324 | "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 325 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 326 | "checksum clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)" = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 327 | "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 328 | "checksum crossterm 0.17.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6f4919d60f26ae233e14233cc39746c8c8bb8cd7b05840ace83604917b51b6c7" 329 | "checksum crossterm_winapi 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "057b7146d02fb50175fd7dbe5158f6097f33d02831f43b4ee8ae4ddf67b68f5c" 330 | "checksum getrandom 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" 331 | "checksum hermit-abi 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c30f6d0bc6b00693347368a67d41b58f2fb851215ff1da49e90fe2c5c667151" 332 | "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 333 | "checksum libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)" = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235" 334 | "checksum lock_api 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" 335 | "checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" 336 | "checksum mio 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e9971bc8349a361217a8f2a41f5d011274686bd4436465ba51730921039d7fb" 337 | "checksum miow 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e" 338 | "checksum ntapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7a31937dea023539c72ddae0e3571deadc1414b300483fa7aaec176168cfa9d2" 339 | "checksum parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" 340 | "checksum parking_lot_core 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" 341 | "checksum ppv-lite86 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20" 342 | "checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 343 | "checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 344 | "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 345 | "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 346 | "checksum redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)" = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 347 | "checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 348 | "checksum signal-hook 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "604508c1418b99dfe1925ca9224829bb2a8a9a04dda655cc01fcad46f4ab05ed" 349 | "checksum signal-hook-registry 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a3e12110bc539e657a646068aaf5eb5b63af9d0c1f7b29c97113fad80e15f035" 350 | "checksum smallvec 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252" 351 | "checksum socket2 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44" 352 | "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 353 | "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 354 | "checksum unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 355 | "checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 356 | "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 357 | "checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 358 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 359 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 360 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "maze_generator" 3 | version = "0.1.0" 4 | authors = ["Mårten Åsberg"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | rand = "0.7.3" 9 | crossterm = "0.17.7" 10 | clap = "2.33.3" 11 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mårten Åsberg 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MazeGenerator 2 | 3 | A collection of algorithms for generating mazes, while visualizing them. 4 | 5 | ## Demos 6 | 7 | Here is a demonstration of mazes being generated with the various algorithms. 8 | 9 | ### Randomized Depth First Search `--dfs` 10 | 11 | ![Animated demo of the algorithm](./animations/dfs.webp) 12 | [Wikipedia](https://en.wikipedia.org/wiki/Maze_generation_algorithm#Randomized_depth-first_search) 13 | [Code](./src/map.rs#L151) 14 | 15 | ### Random Binary Tree Maze `--tree` 16 | 17 | ![Animated demo of the algorithm](./animations/tree.webp) 18 | [Wikipedia](https://en.wikipedia.org/wiki/Maze_generation_algorithm#Simple_algorithms) 19 | [Code](./src/map.rs#L188) 20 | 21 | ### Randomized Prim's algorithm `--prim` 22 | 23 | ![Animated demo of the algorithm](./animations/prim.webp) 24 | [Wikipedia](https://en.wikipedia.org/wiki/Maze_generation_algorithm#Randomized_Prim's_algorithm) 25 | [Code](./src/map.rs#L222) 26 | 27 | ### The Aldous-Broder algorithm `--ab` 28 | 29 | ![Animated demo of the algorithm](./animations/ab.webp) 30 | [Wikipedia](https://en.wikipedia.org/wiki/Maze_generation_algorithm#Aldous-Broder_algorithm) 31 | [Code](./src/map.rs#L253) 32 | 33 | ### Recursive Division Method `--div` 34 | 35 | ![Animated demo of the algorithm](./animations/div.webp) 36 | [Wikipedia](https://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_division_method) 37 | [Code](./src/map.rs#L287) 38 | 39 | ### Wilson's algorithm `--wilson` 40 | 41 | ![Animated demo of the algorithm](./animations/wilson.webp) 42 | [Wikipedia](https://en.wikipedia.org/wiki/Maze_generation_algorithm#Wilson's_algorithm) 43 | [Code](./src/map.rs#L360) 44 | 45 | ## Usage 46 | 47 | ``` 48 | USAGE: 49 | maze_generator [FLAGS] [OPTIONS] 50 | 51 | FLAGS: 52 | --dfs Use the depth first search algorithm for maze generation [default] 53 | --tree Use the binary tree maze algorithm for maze generation 54 | --prim Use Prim's algorithm for maze generation 55 | --ab Use the Aldous-Broder algorithm for maze generation 56 | --div Use the recursive division method for maze generation 57 | --wilson Use Wilson's algorithm (loop-erased random walk) for maze generation 58 | -h, --help Prints help information 59 | 60 | OPTIONS: 61 | --rows Number of rows of the generated map [default: 5] 62 | --columns Number of columns of the generated map [default: 5] 63 | --start_row The row to start generating from [default: 0] 64 | --start_column The column to start generating from [default: 0] 65 | --delay The ms delay between steps [default: 50] 66 | ``` 67 | 68 | ## Development 69 | 70 | This project is developed in Rust and uses Cargo. 71 | 72 | During development you will most likely want to use 73 | ``` 74 | > cargo run 75 | ``` 76 | if you want to send arguments to the program, preface them with an "empty" 77 | double dash 78 | ``` 79 | > cargo run -- 80 | ``` 81 | 82 | For publication use 83 | ``` 84 | > cargo build --release 85 | ``` 86 | the resulting binary will be placed in `./target/release`. 87 | -------------------------------------------------------------------------------- /animations/ab.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/89netraM/MazeGenerator/32f2a511374313787280e0a28079c5b8b3ce4c7a/animations/ab.webp -------------------------------------------------------------------------------- /animations/dfs.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/89netraM/MazeGenerator/32f2a511374313787280e0a28079c5b8b3ce4c7a/animations/dfs.webp -------------------------------------------------------------------------------- /animations/div.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/89netraM/MazeGenerator/32f2a511374313787280e0a28079c5b8b3ce4c7a/animations/div.webp -------------------------------------------------------------------------------- /animations/prim.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/89netraM/MazeGenerator/32f2a511374313787280e0a28079c5b8b3ce4c7a/animations/prim.webp -------------------------------------------------------------------------------- /animations/tree.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/89netraM/MazeGenerator/32f2a511374313787280e0a28079c5b8b3ce4c7a/animations/tree.webp -------------------------------------------------------------------------------- /animations/wilson.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/89netraM/MazeGenerator/32f2a511374313787280e0a28079c5b8b3ce4c7a/animations/wilson.webp -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = true 2 | max_width = 120 3 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use crossterm::{cursor, ExecutableCommand, QueueableCommand}; 2 | use std::io::{stdout, Write}; 3 | 4 | use clap::{App, Arg, ArgGroup, ArgMatches}; 5 | use std::str::FromStr; 6 | 7 | use std::{thread, time::Duration}; 8 | 9 | mod map; 10 | use map::Direction; 11 | use map::Map; 12 | use map::Position; 13 | 14 | fn main() { 15 | let matches = App::new("Maze Generator") 16 | .arg( 17 | Arg::with_name("ROWS") 18 | .long("rows") 19 | .default_value("5") 20 | .validator(check_arg_is_number) 21 | .help("Number of rows of the generated map") 22 | .display_order(0), 23 | ) 24 | .arg( 25 | Arg::with_name("COLUMNS") 26 | .long("columns") 27 | .default_value("5") 28 | .validator(check_arg_is_number) 29 | .help("Number of columns of the generated map") 30 | .display_order(1), 31 | ) 32 | .arg( 33 | Arg::with_name("START_ROW") 34 | .long("start_row") 35 | .default_value("0") 36 | .validator(check_arg_is_number) 37 | .help("The row to start generating from") 38 | .display_order(2), 39 | ) 40 | .arg( 41 | Arg::with_name("START_COLUMN") 42 | .long("start_column") 43 | .default_value("0") 44 | .validator(check_arg_is_number) 45 | .help("The column to start generating from") 46 | .display_order(3), 47 | ) 48 | .arg( 49 | Arg::with_name("DELAY") 50 | .long("delay") 51 | .default_value("50") 52 | .validator(check_arg_is_number) 53 | .help("The ms delay between steps") 54 | .display_order(4), 55 | ) 56 | .arg( 57 | Arg::with_name("DFS") 58 | .long("dfs") 59 | .help("Use the depth first search algorithm for maze generation [default]") 60 | .display_order(5), 61 | ) 62 | .arg( 63 | Arg::with_name("TREE") 64 | .long("tree") 65 | .help("Use the binary tree maze algorithm for maze generation") 66 | .display_order(6), 67 | ) 68 | .arg( 69 | Arg::with_name("PRIM") 70 | .long("prim") 71 | .help("Use Prim's algorithm for maze generation") 72 | .display_order(7), 73 | ) 74 | .arg( 75 | Arg::with_name("AB") 76 | .long("ab") 77 | .help("Use the Aldous-Broder algorithm for maze generation") 78 | .display_order(8), 79 | ) 80 | .arg( 81 | Arg::with_name("DIV") 82 | .long("div") 83 | .help("Use the recursive division method for maze generation") 84 | .display_order(9), 85 | ) 86 | .arg( 87 | Arg::with_name("WILSON") 88 | .long("wilson") 89 | .help("Use Wilson's algorithm (loop-erased random walk) for maze generation") 90 | .display_order(9), 91 | ) 92 | .group(ArgGroup::with_name("ALGORITHM").args(&[ 93 | "DFS", 94 | "TREE", 95 | "PRIM", 96 | "AB", 97 | "DIV", 98 | "WILSON", 99 | ])) 100 | .get_matches(); 101 | 102 | let rows = get_arg_as_t(&matches, "ROWS"); 103 | let columns = get_arg_as_t(&matches, "COLUMNS"); 104 | let start_pos = Position( 105 | get_arg_as_t(&matches, "START_ROW"), 106 | get_arg_as_t(&matches, "START_COLUMN"), 107 | ); 108 | let delay = get_arg_as_t(&matches, "DELAY"); 109 | 110 | let mut stdout = stdout(); 111 | let did_hide = stdout.execute(cursor::Hide).is_ok(); 112 | let initial_peek_fn = |map: &Map| println!("{}", map); 113 | let peek_fn = |map: &Map, pos: &Position, dir: &Direction| { 114 | let chars = map.get_chars(pos, dir); 115 | let rows = (map.rows - pos.0) as u16 + if dir == &Direction::Up { 1 } else { 0 }; 116 | let columns = pos.1 as u16 + if dir == &Direction::Right { 1 } else { 0 }; 117 | 118 | if dir == &Direction::Left || dir == &Direction::Right { 119 | stdout.queue(cursor::MoveUp(rows + 1)).expect("Could not move cursor."); 120 | if columns > 0 { 121 | stdout 122 | .queue(cursor::MoveRight(columns)) 123 | .expect("Could not move cursor."); 124 | } 125 | stdout.write_fmt(format_args!("{}", chars.0)).expect("Could not write."); 126 | stdout.queue(cursor::MoveDown(1)).expect("Could not move cursor."); 127 | stdout.queue(cursor::MoveLeft(1)).expect("Could not move cursor."); 128 | stdout.write_fmt(format_args!("{}", chars.1)).expect("Could not write."); 129 | stdout 130 | .queue(cursor::MoveLeft(columns + 1)) 131 | .expect("Could not move cursor."); 132 | if rows > 0 { 133 | stdout.queue(cursor::MoveDown(rows)).expect("Could not move cursor."); 134 | } 135 | } else { 136 | if rows > 0 { 137 | stdout.queue(cursor::MoveUp(rows)).expect("Could not move cursor."); 138 | } 139 | if columns > 0 { 140 | stdout 141 | .queue(cursor::MoveRight(columns)) 142 | .expect("Could not move cursor."); 143 | } 144 | stdout 145 | .write_fmt(format_args!("{}{}", chars.0, chars.1)) 146 | .expect("Could not write."); 147 | stdout 148 | .queue(cursor::MoveLeft(columns + 2)) 149 | .expect("Could not move cursor."); 150 | if rows > 0 { 151 | stdout.queue(cursor::MoveDown(rows)).expect("Could not move cursor."); 152 | } 153 | } 154 | stdout.flush().expect("Could not flush."); 155 | 156 | if delay > 0 { 157 | thread::sleep(Duration::from_millis(delay)); 158 | } 159 | }; 160 | let map = if matches.is_present("TREE") { 161 | Map::generate_tree(rows, columns, initial_peek_fn, peek_fn) 162 | } else if matches.is_present("PRIM") { 163 | Map::generate_prim(rows, columns, start_pos, initial_peek_fn, peek_fn) 164 | } else if matches.is_present("AB") { 165 | Map::generate_ab(rows, columns, start_pos, initial_peek_fn, peek_fn) 166 | } else if matches.is_present("DIV") { 167 | Map::generate_div(rows, columns, initial_peek_fn, peek_fn) 168 | } else if matches.is_present("WILSON") { 169 | Map::generate_wilson(rows, columns, start_pos, initial_peek_fn, peek_fn) 170 | } else { 171 | Map::generate_dfs(rows, columns, start_pos, initial_peek_fn, peek_fn) 172 | }; 173 | 174 | if did_hide { 175 | stdout.execute(cursor::Show).expect("Could not show cursor."); 176 | } 177 | 178 | if let Some(path) = map.solve(Position(0, 0), Position(map.rows - 1, map.columns - 1)) { 179 | println!( 180 | "Path: {}", 181 | path.into_iter().map(|d| format!("{}", d)).collect::() 182 | ); 183 | } else { 184 | println!("No path through maze"); 185 | } 186 | } 187 | 188 | fn check_arg_is_number(s: String) -> Result<(), String> { 189 | if usize::from_str(&s).is_ok() { 190 | Ok(()) 191 | } else { 192 | Err("Must be a number".to_string()) 193 | } 194 | } 195 | 196 | fn get_arg_as_t(matches: &ArgMatches, name: &str) -> T { 197 | if let Some(s) = matches.value_of(name) { 198 | if let Ok(v) = T::from_str(s) { 199 | return v; 200 | } 201 | } 202 | 203 | panic!(); 204 | } 205 | -------------------------------------------------------------------------------- /src/map.rs: -------------------------------------------------------------------------------- 1 | use rand::seq::SliceRandom; 2 | use rand::{thread_rng, Rng}; 3 | 4 | use std::collections::HashSet; 5 | use std::collections::VecDeque; 6 | use std::collections::{hash_map::Entry, HashMap}; 7 | use std::fmt; 8 | 9 | const UP: usize = 0b1000; 10 | const LEFT: usize = 0b0100; 11 | const RIGHT: usize = 0b0010; 12 | const DOWN: usize = 0b0001; 13 | 14 | #[derive(Copy, Clone)] 15 | pub struct WallJunction(usize); 16 | 17 | impl WallJunction { 18 | fn set(&mut self, bit: usize, activate: bool) { 19 | if activate { 20 | self.0 |= bit; 21 | } else { 22 | self.0 &= !bit; 23 | } 24 | } 25 | fn is(&self, bit: usize) -> bool { 26 | self.0 & bit != 0 27 | } 28 | 29 | pub fn set_up(&mut self, activate: bool) { 30 | self.set(UP, activate) 31 | } 32 | pub fn is_up(&self) -> bool { 33 | self.is(UP) 34 | } 35 | pub fn set_left(&mut self, activate: bool) { 36 | self.set(LEFT, activate) 37 | } 38 | pub fn is_left(&self) -> bool { 39 | self.is(LEFT) 40 | } 41 | pub fn set_right(&mut self, activate: bool) { 42 | self.set(RIGHT, activate) 43 | } 44 | pub fn is_right(&self) -> bool { 45 | self.is(RIGHT) 46 | } 47 | pub fn set_down(&mut self, activate: bool) { 48 | self.set(DOWN, activate) 49 | } 50 | pub fn is_down(&self) -> bool { 51 | self.is(DOWN) 52 | } 53 | } 54 | 55 | impl Default for WallJunction { 56 | fn default() -> Self { 57 | WallJunction(0) 58 | } 59 | } 60 | 61 | impl From for char { 62 | fn from(wj: WallJunction) -> Self { 63 | match wj.0 { 64 | b if b == UP => '╵', 65 | b if b == UP | LEFT => '┘', 66 | b if b == UP | LEFT | RIGHT => '┴', 67 | b if b == UP | LEFT | RIGHT | DOWN => '┼', 68 | b if b == UP | LEFT | DOWN => '┤', 69 | b if b == UP | RIGHT => '└', 70 | b if b == UP | RIGHT | DOWN => '├', 71 | b if b == UP | DOWN => '│', 72 | b if b == LEFT => '╴', 73 | b if b == LEFT | RIGHT => '─', 74 | b if b == LEFT | RIGHT | DOWN => '┬', 75 | b if b == LEFT | DOWN => '┐', 76 | b if b == RIGHT => '╶', 77 | b if b == RIGHT | DOWN => '┌', 78 | b if b == DOWN => '╷', 79 | _ => ' ', 80 | } 81 | } 82 | } 83 | 84 | impl fmt::Display for WallJunction { 85 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 86 | write!(f, "{}", char::from(*self)) 87 | } 88 | } 89 | 90 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 91 | pub enum Direction { 92 | Up, 93 | Left, 94 | Right, 95 | Down, 96 | } 97 | 98 | impl fmt::Display for Direction { 99 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 100 | write!( 101 | f, 102 | "{}", 103 | match self { 104 | Direction::Up => "↑", 105 | Direction::Left => "←", 106 | Direction::Right => "→", 107 | Direction::Down => "↓", 108 | } 109 | ) 110 | } 111 | } 112 | 113 | const UPPER_LEFT: WallJunction = WallJunction(RIGHT | DOWN); 114 | const UPPER_RIGHT: WallJunction = WallJunction(LEFT | DOWN); 115 | const LOWER_LEFT: WallJunction = WallJunction(RIGHT | UP); 116 | const LOWER_RIGHT: WallJunction = WallJunction(LEFT | UP); 117 | const HORIZONTAL: WallJunction = WallJunction(LEFT | RIGHT); 118 | const VERTICAL: WallJunction = WallJunction(UP | DOWN); 119 | 120 | const DIRECTIONS: [Direction; 4] = [Direction::Up, Direction::Left, Direction::Right, Direction::Down]; 121 | 122 | #[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)] 123 | pub struct Position( 124 | /// Row 125 | pub usize, 126 | /// Column 127 | pub usize, 128 | ); 129 | 130 | pub struct Map { 131 | pub rows: usize, 132 | pub columns: usize, 133 | map: Box<[bool]>, 134 | } 135 | 136 | impl Map { 137 | fn new_with_value(rows: usize, columns: usize, value: bool) -> Map { 138 | Map { 139 | rows, 140 | columns, 141 | map: vec![value; rows * 2 * columns - (rows + columns)].into_boxed_slice(), 142 | } 143 | } 144 | pub fn new(rows: usize, columns: usize) -> Map { 145 | Map::new_with_value(rows, columns, true) 146 | } 147 | pub fn new_empty(rows: usize, columns: usize) -> Map { 148 | Map::new_with_value(rows, columns, false) 149 | } 150 | 151 | pub fn generate_dfs(rows: usize, columns: usize, start: Position, mut initial_peek: F, mut peek: G) -> Map 152 | where 153 | F: FnMut(&Map), 154 | G: FnMut(&Map, &Position, &Direction), 155 | { 156 | let mut map = Map::new(rows, columns); 157 | initial_peek(&map); 158 | let mut rng = thread_rng(); 159 | 160 | let mut visited = HashSet::new(); 161 | visited.insert(start); 162 | let mut to_visit = vec![start]; 163 | 164 | let mut moved_positions = Vec::with_capacity(4); 165 | 166 | while let Some(next) = to_visit.pop() { 167 | moved_positions.clear(); 168 | moved_positions.extend( 169 | DIRECTIONS 170 | .iter() 171 | .filter_map(|d| map.move_in_direction(&next, d).map(|m| (m, d))) 172 | .filter(|(m, _)| !visited.contains(&m)), 173 | ); 174 | if moved_positions.len() > 1 { 175 | to_visit.push(next); 176 | } 177 | if let Some((moved, dir)) = moved_positions.choose(&mut rng) { 178 | map.set(&next, dir, false); 179 | to_visit.push(*moved); 180 | visited.insert(*moved); 181 | peek(&map, &next, &dir); 182 | } 183 | } 184 | 185 | map 186 | } 187 | 188 | pub fn generate_tree(rows: usize, columns: usize, mut initial_peek: F, mut peek: G) -> Map 189 | where 190 | F: FnMut(&Map), 191 | G: FnMut(&Map, &Position, &Direction), 192 | { 193 | let mut map = Map::new(rows, columns); 194 | initial_peek(&map); 195 | 196 | for c in 1..map.columns { 197 | map.set_left(&Position(0, c), false); 198 | 199 | peek(&map, &Position(0, c), &Direction::Left); 200 | } 201 | for r in 1..map.rows { 202 | map.set_above(&Position(r, 0), false); 203 | 204 | peek(&map, &Position(r, 0), &Direction::Up); 205 | } 206 | 207 | for r in 1..map.rows { 208 | for c in 1..map.columns { 209 | if rand::random() { 210 | map.set_left(&Position(r, c), false); 211 | peek(&map, &Position(r, c), &Direction::Left); 212 | } else { 213 | map.set_above(&Position(r, c), false); 214 | peek(&map, &Position(r, c), &Direction::Up); 215 | } 216 | } 217 | } 218 | 219 | map 220 | } 221 | 222 | pub fn generate_prim(rows: usize, columns: usize, start: Position, mut initial_peek: F, mut peek: G) -> Map 223 | where 224 | F: FnMut(&Map), 225 | G: FnMut(&Map, &Position, &Direction), 226 | { 227 | let mut map = Map::new(rows, columns); 228 | initial_peek(&map); 229 | let mut rng = thread_rng(); 230 | 231 | let mut visited = HashSet::new(); 232 | visited.insert(start); 233 | let mut walls = map.walls_around(&start); 234 | 235 | while !walls.is_empty() { 236 | let index = rng.gen_range(0, walls.len()); 237 | let (from, dir) = walls.remove(index); 238 | if let Some(to) = map.move_in_direction(&from, &dir) { 239 | if !visited.contains(&to) { 240 | map.set(&from, &dir, false); 241 | 242 | visited.insert(to); 243 | walls.append(&mut map.walls_around(&to)); 244 | 245 | peek(&map, &from, &dir); 246 | } 247 | } 248 | } 249 | 250 | map 251 | } 252 | 253 | pub fn generate_ab(rows: usize, columns: usize, start: Position, mut initial_peek: F, mut peek: G) -> Map 254 | where 255 | F: FnMut(&Map), 256 | G: FnMut(&Map, &Position, &Direction), 257 | { 258 | let mut map = Map::new(rows, columns); 259 | initial_peek(&map); 260 | let mut rng = thread_rng(); 261 | 262 | let mut visited = HashSet::new(); 263 | visited.insert(start); 264 | let mut current = start; 265 | 266 | let mut moved_positions = Vec::with_capacity(4); 267 | while visited.len() < rows * columns { 268 | moved_positions.clear(); 269 | moved_positions.extend( 270 | DIRECTIONS 271 | .iter() 272 | .filter_map(|d| map.move_in_direction(¤t, d).map(|m| (m, d))), 273 | ); 274 | if let Some(moved) = moved_positions.choose(&mut rng) { 275 | if !visited.contains(&moved.0) { 276 | map.set(¤t, moved.1, false); 277 | peek(&map, ¤t, moved.1); 278 | visited.insert(moved.0); 279 | } 280 | current = moved.0; 281 | } 282 | } 283 | 284 | map 285 | } 286 | 287 | pub fn generate_div(rows: usize, columns: usize, mut initial_peek: F, mut peek: G) -> Map 288 | where 289 | F: FnMut(&Map), 290 | G: FnMut(&Map, &Position, &Direction), 291 | { 292 | fn recurse_vertical(map: &mut Map, rng: &mut R, upper_left: Position, lower_right: Position, peek: &mut G) 293 | where 294 | R: Rng + ?Sized, 295 | G: FnMut(&Map, &Position, &Direction), 296 | { 297 | if upper_left.1 < lower_right.1 { 298 | let div = rng.gen_range(upper_left.1, lower_right.1); 299 | let passage = rng.gen_range(upper_left.0, lower_right.0 + 1); 300 | 301 | for r in upper_left.0..(lower_right.0 + 1) { 302 | if r != passage { 303 | map.set_right(&Position(r, div), true); 304 | peek(&map, &Position(r, div), &Direction::Right); 305 | } 306 | } 307 | 308 | if upper_left.0 >= lower_right.0 { 309 | recurse_vertical(map, rng, upper_left, Position(lower_right.0, div), peek); 310 | recurse_vertical(map, rng, Position(upper_left.0, div + 1), lower_right, peek); 311 | } else { 312 | recurse_horizontal(map, rng, upper_left, Position(lower_right.0, div), peek); 313 | recurse_horizontal(map, rng, Position(upper_left.0, div + 1), lower_right, peek); 314 | } 315 | } 316 | } 317 | fn recurse_horizontal( 318 | map: &mut Map, 319 | rng: &mut R, 320 | upper_left: Position, 321 | lower_right: Position, 322 | peek: &mut G, 323 | ) where 324 | R: Rng + ?Sized, 325 | G: FnMut(&Map, &Position, &Direction), 326 | { 327 | if upper_left.0 < lower_right.0 { 328 | let div = rng.gen_range(upper_left.0, lower_right.0); 329 | let passage = rng.gen_range(upper_left.1, lower_right.1 + 1); 330 | 331 | for c in upper_left.1..(lower_right.1 + 1) { 332 | if c != passage { 333 | map.set_below(&Position(div, c), true); 334 | peek(&map, &Position(div, c), &Direction::Down); 335 | } 336 | } 337 | 338 | if upper_left.1 >= lower_right.1 { 339 | recurse_horizontal(map, rng, upper_left, Position(div, lower_right.1), peek); 340 | recurse_horizontal(map, rng, Position(div + 1, upper_left.1), lower_right, peek); 341 | } else { 342 | recurse_vertical(map, rng, upper_left, Position(div, lower_right.1), peek); 343 | recurse_vertical(map, rng, Position(div + 1, upper_left.1), lower_right, peek); 344 | } 345 | } 346 | } 347 | 348 | let mut map = Map::new_empty(rows, columns); 349 | initial_peek(&map); 350 | let mut rng = thread_rng(); 351 | 352 | let upper_left = Position(0, 0); 353 | let lower_right = Position(map.rows - 1, map.columns - 1); 354 | recurse_vertical(&mut map, &mut rng, upper_left, lower_right, &mut peek); 355 | 356 | map 357 | } 358 | 359 | pub fn generate_wilson(rows: usize, columns: usize, start: Position, mut initial_peek: F, mut peek: G) -> Map 360 | where 361 | F: FnMut(&Map), 362 | G: FnMut(&Map, &Position, &Direction), 363 | { 364 | let mut map = Map::new(rows, columns); 365 | initial_peek(&map); 366 | let mut rng = thread_rng(); 367 | 368 | let mut in_map = HashSet::new(); 369 | in_map.insert(start); 370 | let mut unvisited: Vec<_> = (0..rows) 371 | .flat_map(|r| (0..columns).filter_map(move |c| Some(Position(r, c)).filter(|p| p != &start))) 372 | .collect(); 373 | 374 | let mut path: Vec<(Position, Direction)> = Vec::new(); 375 | let mut moved_positions = Vec::with_capacity(4); 376 | while !unvisited.is_empty() { 377 | let mut current = unvisited[rng.gen_range(0, unvisited.len())]; 378 | 379 | while !in_map.contains(¤t) { 380 | moved_positions.clear(); 381 | moved_positions.extend( 382 | DIRECTIONS 383 | .iter() 384 | .filter_map(|d| map.move_in_direction(¤t, d).map(|m| (m, d))), 385 | ); 386 | if let Some((next, direction)) = moved_positions.choose(&mut rng) { 387 | if let Some(index) = path.iter().position(|p| &p.0 == next) { 388 | for (p, d) in path.drain(index..).rev() { 389 | map.set(&p, &d, true); 390 | peek(&map, &p, &d); 391 | } 392 | } else { 393 | map.set(¤t, direction, false); 394 | peek(&map, ¤t, direction); 395 | path.push((current, **direction)); 396 | } 397 | current = *next; 398 | } 399 | } 400 | 401 | for (p, _) in path.drain(..) { 402 | in_map.insert(p); 403 | unvisited.swap_remove(unvisited.iter().position(|u| u == &p).unwrap()); 404 | } 405 | } 406 | 407 | map 408 | } 409 | 410 | pub fn set_above(&mut self, pos: &Position, closed: bool) { 411 | self.set_below(&Position(pos.0 - 1, pos.1), closed); 412 | } 413 | pub fn is_above(&self, pos: &Position) -> bool { 414 | self.is_below(&Position(pos.0 - 1, pos.1)) 415 | } 416 | pub fn set_left(&mut self, pos: &Position, closed: bool) { 417 | self.set_right(&Position(pos.0, pos.1 - 1), closed); 418 | } 419 | pub fn is_left(&self, pos: &Position) -> bool { 420 | self.is_right(&Position(pos.0, pos.1 - 1)) 421 | } 422 | pub fn set_right(&mut self, pos: &Position, closed: bool) { 423 | assert!(pos.0 < self.rows && pos.1 < self.columns - 1); 424 | 425 | self.map[(self.rows - 1) * self.columns + pos.0 * (self.columns - 1) + pos.1] = closed; 426 | } 427 | pub fn is_right(&self, pos: &Position) -> bool { 428 | assert!(pos.0 < self.rows && pos.1 < self.columns - 1); 429 | 430 | self.map[(self.rows - 1) * self.columns + pos.0 * (self.columns - 1) + pos.1] 431 | } 432 | pub fn set_below(&mut self, pos: &Position, closed: bool) { 433 | assert!(pos.0 < self.rows - 1 && pos.1 < self.columns); 434 | 435 | self.map[pos.0 * self.columns + pos.1] = closed; 436 | } 437 | pub fn is_below(&self, pos: &Position) -> bool { 438 | assert!(pos.0 < self.rows - 1 && pos.1 < self.columns); 439 | 440 | self.map[pos.0 * self.columns + pos.1] 441 | } 442 | 443 | pub fn set(&mut self, pos: &Position, dir: &Direction, closed: bool) { 444 | match dir { 445 | Direction::Up => self.set_above(pos, closed), 446 | Direction::Left => self.set_left(pos, closed), 447 | Direction::Right => self.set_right(pos, closed), 448 | Direction::Down => self.set_below(pos, closed), 449 | }; 450 | } 451 | pub fn is(&self, pos: &Position, dir: &Direction) -> Option { 452 | match dir { 453 | Direction::Up if 0 < pos.0 && pos.0 < self.rows && pos.1 < self.columns => Some(self.is_above(pos)), 454 | Direction::Left if pos.0 < self.rows && 0 < pos.1 && pos.1 < self.columns => Some(self.is_left(pos)), 455 | Direction::Right if pos.0 < self.rows && pos.1 < self.columns - 1 => Some(self.is_right(pos)), 456 | Direction::Down if pos.0 < self.rows - 1 && pos.1 < self.columns => Some(self.is_below(pos)), 457 | _ => None, 458 | } 459 | } 460 | 461 | fn move_in_direction(&self, current: &Position, dir: &Direction) -> Option { 462 | match dir { 463 | Direction::Up if current.0 > 0 => Some(Position(current.0 - 1, current.1)), 464 | Direction::Left if current.1 > 0 => Some(Position(current.0, current.1 - 1)), 465 | Direction::Right if current.1 < self.columns - 1 => Some(Position(current.0, current.1 + 1)), 466 | Direction::Down if current.0 < self.rows - 1 => Some(Position(current.0 + 1, current.1)), 467 | _ => None, 468 | } 469 | } 470 | 471 | fn walls_around(&self, pos: &Position) -> Vec<(Position, Direction)> { 472 | DIRECTIONS 473 | .iter() 474 | .filter_map(|dir| { 475 | if self.is(pos, dir) == Some(true) { 476 | return Some((*pos, *dir)); 477 | } 478 | None 479 | }) 480 | .collect() 481 | } 482 | 483 | fn possible_moves_for(&self, pos: &Position) -> Vec { 484 | DIRECTIONS 485 | .iter() 486 | .filter_map(|dir| { 487 | if self.is(pos, dir) == Some(false) { 488 | return self.move_in_direction(pos, dir); 489 | } 490 | None 491 | }) 492 | .collect() 493 | } 494 | 495 | pub fn solve(&self, from: Position, to: Position) -> Option> { 496 | assert!(from.0 < self.rows && from.1 < self.columns); 497 | assert!(to.0 < self.rows && to.1 < self.columns); 498 | 499 | if from == to { 500 | return Some(Vec::new()); 501 | } 502 | 503 | let mut from_to = HashMap::new(); 504 | from_to.insert(from, None); 505 | let mut to_visit = VecDeque::new(); 506 | to_visit.push_back(from); 507 | 508 | while let Some(next) = to_visit.pop_front() { 509 | for moved in self.possible_moves_for(&next) { 510 | if let Entry::Vacant(e) = from_to.entry(moved) { 511 | e.insert(Some(next)); 512 | if moved == to { 513 | return Some(build_path(from_to, to)); 514 | } 515 | to_visit.push_back(moved); 516 | } 517 | } 518 | } 519 | 520 | None 521 | } 522 | 523 | pub fn get_chars(&self, pos: &Position, dir: &Direction) -> (char, char) { 524 | if dir == &Direction::Left || dir == &Direction::Right { 525 | let mut above = WallJunction::default(); 526 | let mut below = WallJunction::default(); 527 | 528 | if self.is(pos, dir).unwrap_or(true) { 529 | above.set_down(true); 530 | below.set_up(true); 531 | } 532 | 533 | if pos.0 != 0 && self.is(&Position(pos.0 - 1, pos.1), dir).unwrap_or(true) { 534 | above.set_up(true); 535 | } 536 | if pos.0 != self.rows - 1 && self.is(&Position(pos.0 + 1, pos.1), dir).unwrap_or(true) { 537 | below.set_down(true); 538 | } 539 | 540 | if dir == &Direction::Left { 541 | if pos.0 == 0 || self.is_above(pos) { 542 | above.set_right(true); 543 | } 544 | if pos.0 == self.rows - 1 || self.is_below(pos) { 545 | below.set_right(true); 546 | } 547 | if pos.1 != 0 { 548 | if pos.0 == 0 || self.is_above(&Position(pos.0, pos.1 - 1)) { 549 | above.set_left(true); 550 | } 551 | if pos.0 == self.rows - 1 || self.is_below(&Position(pos.0, pos.1 - 1)) { 552 | below.set_left(true); 553 | } 554 | } 555 | } else { 556 | if pos.0 == 0 || self.is_above(pos) { 557 | above.set_left(true); 558 | } 559 | if pos.0 == self.rows - 1 || self.is_below(pos) { 560 | below.set_left(true); 561 | } 562 | if pos.1 != self.columns - 1 { 563 | if pos.0 == 0 || self.is_above(&Position(pos.0, pos.1 + 1)) { 564 | above.set_right(true); 565 | } 566 | if pos.0 == self.rows - 1 || self.is_below(&Position(pos.0, pos.1 + 1)) { 567 | below.set_right(true); 568 | } 569 | } 570 | } 571 | 572 | (char::from(above), char::from(below)) 573 | } else { 574 | let mut left = WallJunction::default(); 575 | let mut right = WallJunction::default(); 576 | 577 | if self.is(pos, dir).unwrap_or(true) { 578 | left.set_right(true); 579 | right.set_left(true); 580 | } 581 | 582 | if pos.1 != 0 && self.is(&Position(pos.0, pos.1 - 1), dir).unwrap_or(true) { 583 | left.set_left(true); 584 | } 585 | if pos.1 != self.columns - 1 && self.is(&Position(pos.0, pos.1 + 1), dir).unwrap_or(true) { 586 | right.set_right(true); 587 | } 588 | 589 | if dir == &Direction::Up { 590 | if pos.1 == 0 || self.is_left(pos) { 591 | left.set_down(true); 592 | } 593 | if pos.1 == self.columns - 1 || self.is_right(pos) { 594 | right.set_down(true); 595 | } 596 | if pos.0 != 0 { 597 | if pos.1 == 0 || self.is_left(&Position(pos.0 - 1, pos.1)) { 598 | left.set_up(true); 599 | } 600 | if pos.1 == self.columns - 1 || self.is_right(&Position(pos.0 - 1, pos.1)) { 601 | right.set_up(true); 602 | } 603 | } 604 | } else { 605 | if pos.1 == 0 || self.is_left(pos) { 606 | left.set_up(true); 607 | } 608 | if pos.1 == self.columns - 1 || self.is_right(pos) { 609 | right.set_up(true); 610 | } 611 | if pos.0 != self.rows - 1 { 612 | if pos.1 == 0 || self.is_left(&Position(pos.0 + 1, pos.1)) { 613 | left.set_down(true); 614 | } 615 | if pos.1 == self.columns - 1 || self.is_right(&Position(pos.0 + 1, pos.1)) { 616 | right.set_down(true); 617 | } 618 | } 619 | } 620 | 621 | (char::from(left), char::from(right)) 622 | } 623 | } 624 | } 625 | 626 | fn build_path(mut from_to: HashMap>, to: Position) -> Vec { 627 | if let Some(Some(from)) = from_to.remove(&to) { 628 | let mut part = build_path(from_to, from); 629 | part.push( 630 | match ((from.0 as isize) - (to.0 as isize), (from.1 as isize) - (to.1 as isize)) { 631 | (1, 0) => Direction::Up, 632 | (0, 1) => Direction::Left, 633 | (0, -1) => Direction::Right, 634 | (-1, 0) => Direction::Down, 635 | (_, _) => panic!(), 636 | }, 637 | ); 638 | part 639 | } else { 640 | Vec::new() 641 | } 642 | } 643 | 644 | impl fmt::Display for Map { 645 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 646 | let mut above; 647 | let mut below = Vec::with_capacity(self.columns + 1); 648 | 649 | below.push(UPPER_LEFT); 650 | below.append(&mut vec![HORIZONTAL; self.columns - 1]); 651 | below.push(UPPER_RIGHT); 652 | 653 | for r in 0..(self.rows - 1) { 654 | above = below; 655 | below = vec![WallJunction::default(); self.columns + 1]; 656 | below[0] = VERTICAL; 657 | below[self.columns] = VERTICAL; 658 | 659 | for c in 0..(self.columns - 1) { 660 | above[c + 1].set_down(self.is_right(&Position(r, c))); 661 | below[c + 1].set_up(self.is_right(&Position(r, c))); 662 | } 663 | 664 | for c in 0..self.columns { 665 | below[c].set_right(self.is_below(&Position(r, c))); 666 | below[c + 1].set_left(self.is_below(&Position(r, c))); 667 | } 668 | 669 | writeln!(f, "{}", above.into_iter().map(|j| format!("{}", j)).collect::())?; 670 | } 671 | 672 | above = below; 673 | below = vec![HORIZONTAL; self.columns + 1]; 674 | below[0] = LOWER_LEFT; 675 | below[self.columns] = LOWER_RIGHT; 676 | for c in 0..(self.columns - 1) { 677 | above[c + 1].set_down(self.is_right(&Position(self.rows - 1, c))); 678 | below[c + 1].set_up(self.is_right(&Position(self.rows - 1, c))); 679 | } 680 | 681 | writeln!(f, "{}", above.into_iter().map(|j| format!("{}", j)).collect::())?; 682 | write!(f, "{}", below.into_iter().map(|j| format!("{}", j)).collect::()) 683 | } 684 | } 685 | --------------------------------------------------------------------------------