├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── demo.gif ├── screenshot.png ├── screenshot2.png ├── screenshot3.png └── src ├── common.rs ├── grid.rs ├── heightgrid.rs ├── main.rs ├── pipegrid.rs ├── point.rs ├── rectangle.rs └── roomgrid.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "procmapgen" 3 | version = "0.1.0" 4 | authors = ["Maarten van Gompel "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | clap = "2.33.0" 9 | rand = "0.7.0" 10 | rand_pcg = "0.2.0" 11 | num = "0.2.0" 12 | #ansi_colours = "^1.0" 13 | ansi_term = "0.12.0" 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Procedural Map Generator 2 | 3 | A small toy project written in Rust: procedural generation of 2D grid-based maps with simple terminal-based visualisations 4 | 5 | ![Video](https://raw.githubusercontent.com/proycon/procmapgen/master/demo.gif) 6 | 7 | There are three kinds of graphs, and different styles of visualisation: 8 | 9 | * ``Pipe maps`` - an interconnected network of pipes/roads/subways/hallways or whatever you see in it. 10 | * No isolated subgraphs. 11 | * Two classes of pipes, a 'backbone' or set of core pipes (thicker) vs 'regular' 12 | * Simple visualisation (using unicode block drawing) to standard output 13 | * ``Height maps`` - Each cell has a height, good for landscapes. Can also be visualised as a heat map, terrain map. 14 | * ``Room maps`` - Rooms with corridors. 15 | 16 | 17 | ## Screenshots 18 | 19 | ![Screenshot](https://raw.githubusercontent.com/proycon/procmapgen/master/screenshot.png) 20 | 21 | ![Screenshot](https://raw.githubusercontent.com/proycon/procmapgen/master/screenshot2.png) 22 | 23 | ![Screenshot](https://raw.githubusercontent.com/proycon/procmapgen/master/screenshot3.png) 24 | 25 | ## Build instructions 26 | 27 | Assumes you have Rust and Cargo installed: 28 | 29 | ``` 30 | $ git clone https://github.com/proycon/procmapgen 31 | $ cd procmapgen 32 | $ cargo build --release 33 | ``` 34 | 35 | Usage: 36 | 37 | ``` 38 | $ cargo run -- --help 39 | ``` 40 | 41 | It's fun to use this with ``--loop 250`` to see random ones continuously. The number is the amount of milliseconds to 42 | wait between maps: 43 | 44 | ``` 45 | $ cargo run -- --loop 250 --type height 46 | ``` 47 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/proycon/procmapgen/f441807379b5b55de387f374d5548a8a420d9396/demo.gif -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/proycon/procmapgen/f441807379b5b55de387f374d5548a8a420d9396/screenshot.png -------------------------------------------------------------------------------- /screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/proycon/procmapgen/f441807379b5b55de387f374d5548a8a420d9396/screenshot2.png -------------------------------------------------------------------------------- /screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/proycon/procmapgen/f441807379b5b55de387f374d5548a8a420d9396/screenshot3.png -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | use num::{Integer,FromPrimitive,ToPrimitive}; 2 | 3 | #[derive(Debug,Clone,Copy)] 4 | pub enum Direction { 5 | North, 6 | East, 7 | South, 8 | West, 9 | } 10 | 11 | 12 | pub trait Distance { 13 | fn distance(&self, other: &Self) -> f64; 14 | } 15 | 16 | pub trait Volume where 17 | ScaleType: Integer + FromPrimitive + ToPrimitive + Copy { 18 | 19 | fn area(&self) -> f64 { 20 | self.width().to_f64().expect("Conversion problem") * self.height().to_f64().expect("Conversion problem") 21 | } 22 | 23 | //Is the bounding box for this volume square? 24 | fn is_square(&self) -> bool { 25 | self.width() == self.height() 26 | } 27 | 28 | fn width(&self) -> ScaleType; 29 | fn height(&self) -> ScaleType; 30 | 31 | fn intersects(&self, other: &Self) -> bool; 32 | } 33 | 34 | ///Implementing my own min() function because cmp::min() doesn't to floats 35 | pub fn fmin(x: f64, y: f64) -> f64 { 36 | if x < y { 37 | x 38 | } else { 39 | y 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/grid.rs: -------------------------------------------------------------------------------- 1 | use rand::{SeedableRng,Rng}; 2 | use rand_pcg::Pcg32; 3 | use num::{Integer,Num,FromPrimitive,ToPrimitive,Bounded,range,CheckedAdd,CheckedSub}; 4 | use std::ops::{Index,Add,AddAssign}; 5 | use std::cmp::{min,max,PartialEq,Eq,Ord,PartialOrd,Ordering}; 6 | use std::fmt; 7 | use std::iter::{Iterator,FromIterator}; 8 | use std::collections::BinaryHeap; 9 | use ansi_term; 10 | 11 | use crate::common::{Distance,Direction}; 12 | use crate::point::Point; 13 | use crate::rectangle::{Rectangle,RectIterator}; 14 | 15 | 16 | ///The basic grid type 17 | #[derive(Clone,PartialEq)] 18 | pub struct Grid { 19 | ///A flattened vector 20 | data: Vec, 21 | 22 | ///The dimensions of the grid 23 | size: (ScaleType,ScaleType), 24 | } 25 | 26 | ///An iterator over all points and (references to) values in the grid 27 | pub struct GridIterator<'a, ScaleType, ValueType: 'a> { 28 | grid: &'a Grid, 29 | current: RectIterator, 30 | } 31 | 32 | pub trait GenericGrid where 33 | ScaleType: Integer + FromPrimitive + ToPrimitive + Bounded + Copy, 34 | ValueType: Default + PartialEq + Clone { 35 | 36 | fn new(width: ScaleType, height: ScaleType) -> Grid { 37 | Self::new_init(width, height, ValueType::default()) 38 | } 39 | 40 | fn new_init(width: ScaleType, height: ScaleType, value: ValueType) -> Grid { 41 | //create initial empty 2D grid 42 | let size = width.to_usize().unwrap() * height.to_usize().unwrap(); 43 | let mut grid: Vec = Vec::with_capacity(size); //flattened grid 44 | for _ in 0..size { 45 | grid.push(value.clone()); 46 | } 47 | 48 | Grid { 49 | data: grid, 50 | size: (width, height), 51 | } 52 | } 53 | 54 | 55 | 56 | fn rectangle(&self) -> Rectangle { 57 | Rectangle::new_dims(ScaleType::zero(), ScaleType::zero(), self.width(), self.height()) 58 | } 59 | 60 | fn width_as_usize(&self) -> usize { 61 | self.width().to_usize().expect("Conversion error") 62 | } 63 | 64 | fn height_as_usize(&self) -> usize { 65 | self.height().to_usize().expect("Conversion error") 66 | } 67 | 68 | 69 | ///Point to Index 70 | fn index(&self, point: &Point) -> usize { 71 | (point.y() * self.width() + point.x()).to_usize().expect("Unable to cast to usize") 72 | } 73 | 74 | ///Index to Point 75 | fn point(&self, index: usize) -> Point { 76 | let y = index / self.width_as_usize(); 77 | let x = index % self.width_as_usize(); 78 | Point(ScaleType::from_usize(x).unwrap(), ScaleType::from_usize(y).unwrap()) 79 | } 80 | 81 | fn set(&mut self, point: &Point, value: ValueType) -> bool { 82 | if let Some(v) = self.get_mut(point) { 83 | *v = value; 84 | return true; 85 | } 86 | return false; 87 | } 88 | 89 | fn is_set(&self, point: &Point) -> bool { 90 | *self.get(point).expect("Unable to unpack") != ValueType::default() 91 | } 92 | 93 | fn set_index(&mut self, index: usize, value: ValueType) -> bool { 94 | if let Some(mut v) = self.get_mut_by_index(index) { 95 | *v = value; 96 | true 97 | } else { 98 | false 99 | } 100 | } 101 | 102 | fn hasneighbour(&self, point: &Point, direction: Direction) -> bool { 103 | if let Some(neighbour) = point.neighbour(direction, Some(self.width()), Some(self.height())) { 104 | match self.get(&neighbour) { 105 | Some(value) if *value != ValueType::default() => true, 106 | _ => false, 107 | } 108 | } else { 109 | false 110 | } 111 | } 112 | 113 | fn hasneighbours(&self,point: &Point) -> (bool, bool, bool, bool) { 114 | ( 115 | self.hasneighbour(point, Direction::North), 116 | self.hasneighbour(point, Direction::East), 117 | self.hasneighbour(point, Direction::South), 118 | self.hasneighbour(point, Direction::West), 119 | ) 120 | } 121 | 122 | fn countneighbours(&self, point: &Point) -> usize { 123 | let mut count = 0; 124 | if self.hasneighbour(point, Direction::North) { count += 1 }; 125 | if self.hasneighbour(point, Direction::East) { count += 1 }; 126 | if self.hasneighbour(point, Direction::South) { count += 1 }; 127 | if self.hasneighbour(point, Direction::West) { count += 1 }; 128 | count 129 | } 130 | 131 | fn getneighbour(&self, point: &Point, direction: Direction) -> Option> { 132 | if let Some(neighbour) = point.neighbour(direction, Some(self.width()), Some(self.height())) { 133 | Some(neighbour) 134 | } else { 135 | None 136 | } 137 | } 138 | 139 | fn getneighbours(&self, point: &Point) -> Vec> { 140 | let mut neighbours = Vec::new(); 141 | if let Some(neighbour) = self.getneighbour(point, Direction::North) { neighbours.push(neighbour); } 142 | if let Some(neighbour) = self.getneighbour(point, Direction::East) { neighbours.push(neighbour); } 143 | if let Some(neighbour) = self.getneighbour(point, Direction::South) { neighbours.push(neighbour); } 144 | if let Some(neighbour) = self.getneighbour(point, Direction::West) { neighbours.push(neighbour); } 145 | neighbours 146 | } 147 | 148 | 149 | fn get(&self, point: &Point) -> Option<&ValueType> { 150 | self.get_data_vec().get(self.index(point)) 151 | } 152 | 153 | fn get_mut(&mut self, point: &Point) -> Option<&mut ValueType> { 154 | let index = self.index(point); 155 | self.get_mut_data_vec().get_mut(index) 156 | } 157 | 158 | fn get_by_index(&self, index: usize) -> Option<&ValueType> { 159 | self.get_data_vec().get(index) 160 | } 161 | 162 | fn get_mut_by_index(&mut self, index: usize) -> Option<&mut ValueType> { 163 | self.get_mut_data_vec().get_mut(index) 164 | } 165 | 166 | //methods that need to be implemented: 167 | fn width(&self) -> ScaleType; 168 | fn height(&self) -> ScaleType; 169 | fn iter(&self) -> GridIterator; 170 | fn get_data_vec(&self) -> &Vec; 171 | fn get_mut_data_vec(&mut self) -> &mut Vec; 172 | 173 | } 174 | 175 | impl GenericGrid for Grid where 176 | ScaleType: Integer + FromPrimitive + ToPrimitive + Bounded + Copy, 177 | ValueType: Default + PartialEq + Clone { 178 | 179 | 180 | fn width(&self) -> ScaleType { 181 | self.size.0 182 | } 183 | 184 | fn height(&self) -> ScaleType { 185 | self.size.1 186 | } 187 | 188 | fn iter(&self) -> GridIterator { 189 | GridIterator { grid: &self, current: self.rectangle().iter() } 190 | } 191 | 192 | 193 | fn get_data_vec(&self) -> &Vec { 194 | &self.data 195 | } 196 | 197 | fn get_mut_data_vec(&mut self) -> &mut Vec { 198 | &mut self.data 199 | } 200 | } 201 | 202 | pub trait NumericGrid<'a,ScaleType, ValueType>: GenericGrid + Index<&'a Point> where 203 | ScaleType: 'a + Integer + FromPrimitive + ToPrimitive + Bounded + Copy, 204 | ValueType: 'a + Num + Default + FromPrimitive + ToPrimitive + PartialOrd + PartialEq + Bounded + CheckedAdd + CheckedSub + Copy { 205 | 206 | fn max(&self) -> ValueType { 207 | let mut largest: Option = None; 208 | for (_, v) in self.iter() { 209 | if largest.is_none() || largest.unwrap() < *v { 210 | largest = Some(*v); 211 | } 212 | } 213 | largest.expect("Grid has no data") 214 | } 215 | 216 | fn min(&self) -> ValueType { 217 | let mut smallest: Option = None; 218 | for (_,v) in self.iter() { 219 | if smallest.is_none() || smallest.unwrap() > *v { 220 | smallest = Some(*v); 221 | } 222 | } 223 | smallest.expect("Grid has no data") 224 | } 225 | 226 | ///Clones the grid with a different ValueType and runs the map function 227 | fn map_into(&self, f: impl Fn(Point, ToValueType) -> ToValueType ) -> Grid where 228 | ToValueType: Num + Default + FromPrimitive + ToPrimitive + PartialOrd + PartialEq + Bounded + CheckedAdd + CheckedSub + Copy { 229 | 230 | let mut clone: Grid = Grid::new(self.width(), self.height()); 231 | for (i, (point, value)) in self.iter().enumerate() { 232 | //MAYBE TODO: add fallback conversion options? 233 | let tovalue = ToValueType::from_usize(value.to_usize().expect("map_into(): Unable to convert to usize")).expect("map_into(): unable to convert from usize"); 234 | clone.set_index(i, f(point, tovalue)); 235 | } 236 | clone 237 | } 238 | 239 | fn inc(&mut self, point: &Point, amount: ValueType) -> bool { 240 | let mut value = self.get_mut(point).expect("Point not found in grid!"); 241 | if let Some(result) = value.checked_add(&amount) { 242 | *value = result; 243 | true 244 | } else { 245 | //value is saturated 246 | *value = ValueType::max_value(); 247 | false 248 | } 249 | } 250 | 251 | fn dec(&mut self, point: &Point, amount: ValueType) -> bool { 252 | let mut value = self.get_mut(point).expect("Point not found in grid!"); 253 | if let Some(result) = value.checked_sub(&amount) { 254 | *value = result; 255 | true 256 | } else { 257 | //value is saturated 258 | *value = ValueType::min_value(); 259 | false 260 | } 261 | } 262 | 263 | fn randompathto(&mut self, rng: &mut Pcg32, from: &Point, to: &Point, value: ValueType) { 264 | let mut retry = true; 265 | let mut retries = 0; 266 | let mut walk = *from; //copy 267 | while retry { 268 | let dx = if to.x() > walk.x() { Direction::East } else { Direction::West }; 269 | let dy = if to.y() > walk.y() { Direction::South } else { Direction::North }; 270 | let mut iteration = 0; 271 | retry = false; 272 | while walk != *to { 273 | if walk != *to { 274 | if !self.is_set(&walk) { 275 | self.set(&walk,value); 276 | } else if iteration == 1 && retries < 5 { 277 | //first step must be to a node that is still empty, restart: 278 | retry = true; 279 | retries += 1; 280 | break; 281 | } 282 | } 283 | if (walk.x() != to.x()) && ((walk.y() == to.y()) || rng.gen()) { 284 | walk = walk.neighbour(dx, Some(self.width()), Some(self.height())).expect("Bumped into boundary, shouldn't happen"); 285 | } else if (walk.y() != to.y()) && ((walk.x() == to.x()) || rng.gen()) { 286 | walk = walk.neighbour(dy, Some(self.width()), Some(self.height())).expect("Bumped into boundary, shouldn't happen"); 287 | } 288 | iteration += 1; 289 | } 290 | } 291 | } 292 | 293 | ///Creates a rectangular path (only horizontal and vertical) between points A and B 294 | fn rectpathto(&mut self, rng: &mut Pcg32, from: &Point, to: &Point, value: ValueType) { 295 | if from == to { 296 | return; 297 | } 298 | let dx = if to.x() > from.x() { Direction::East } else { Direction::West }; 299 | let dy = if to.y() > from.y() { Direction::South } else { Direction::North }; 300 | let horizontal_first: bool = rng.gen(); 301 | let xrange = range(min(from.x(),to.x()), max(from.x(),to.x()) + ScaleType::one()); 302 | let yrange = range(min(from.y(),to.y()), max(from.y(),to.y()) + ScaleType::one()); 303 | if horizontal_first { 304 | for x in xrange { 305 | let point = Point(x,from.y() ); 306 | if self.get(&point) == Some(&ValueType::zero()) { self.set(&point,value); }; 307 | } 308 | for y in yrange { 309 | let point = Point(to.x(), y ); 310 | if self.get(&point) == Some(&ValueType::zero()) { self.set(&point,value); }; 311 | } 312 | } else { 313 | for y in yrange { 314 | let point = Point(from.x(), y ); 315 | if self.get(&point) == Some(&ValueType::zero()) { self.set(&point,value); }; 316 | } 317 | for x in xrange { 318 | let point = Point(x,to.y() ); 319 | if self.get(&point) == Some(&ValueType::zero()) { self.set(&point,value); }; 320 | } 321 | } 322 | 323 | } 324 | 325 | fn add(&mut self, other: &Self) { 326 | let width = min(self.width(), other.width()); 327 | let height = min(self.height(), other.height()); 328 | 329 | for x in range(ScaleType::zero(),width) { 330 | for y in range(ScaleType::zero(),height) { 331 | let point = Point(x,y); 332 | if let Some(value) = other.get(&point) { 333 | self.inc(&point, *value); 334 | } 335 | } 336 | } 337 | } 338 | 339 | fn sub(&mut self, other: &Self) { 340 | let width = min(self.width(), other.width()); 341 | let height = min(self.height(), other.height()); 342 | 343 | for x in range(ScaleType::zero(),width) { 344 | for y in range(ScaleType::zero(),height) { 345 | let point = Point(x,y); 346 | if let Some(value) = other.get(&point) { 347 | self.dec(&point, *value); 348 | } 349 | } 350 | } 351 | } 352 | 353 | } 354 | 355 | impl<'a, ScaleType,ValueType> NumericGrid<'a, ScaleType,ValueType> for Grid where 356 | ScaleType: 'a + Integer + FromPrimitive + ToPrimitive + Bounded + Copy, 357 | ValueType: 'a + Num + Default + FromPrimitive + ToPrimitive + PartialOrd + PartialEq + Bounded + CheckedAdd + CheckedSub + Copy { 358 | 359 | } 360 | 361 | 362 | 363 | impl fmt::Display for Grid where 364 | ScaleType: Integer + FromPrimitive + ToPrimitive + Bounded + Copy, 365 | ValueType: fmt::Display + Default + PartialEq + Clone { 366 | 367 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 368 | for (i, (point, value)) in self.iter().enumerate() { 369 | if point.x() == ScaleType::zero() && i > 0 { 370 | write!(f, "\n")?; 371 | } 372 | write!(f, "{}", value)?; 373 | } 374 | Ok(()) 375 | } 376 | } 377 | 378 | 379 | 380 | 381 | /* 382 | ///Dijkstra pathfinding algorithm 383 | fn findpath(&self, from: &Point, to: &Point, costgrid: Option>) -> Vec> { 384 | 385 | let mut fringe: BinaryHeap> = BinaryHeap::new(); 386 | 387 | //Maintains current distance from "from" to each node, initialise to the highest possible 388 | //value 389 | let mut dist: Grid = Grid::new_init(self.width(), self.height(), u32::max_value()); 390 | 391 | let costgrid = costgrid.unwrap_or(self.map_into( 392 | |_point,value| { 393 | if value == 0 { 0 } else { 1 } //0 means inaccessible 394 | } 395 | )); 396 | 397 | //push the start 398 | dist.set(from, 0); 399 | fringe.push(PathState { point: *from, cost: 0 }); 400 | 401 | while let Some(PathState { point, cost }) = fringe.pop() { 402 | if point == *to { 403 | 404 | } 405 | 406 | if cost > dist[&point] { 407 | continue; 408 | } 409 | 410 | 411 | //Expand the neighbour nodes, 412 | for neighbour in self.getneighbours(&point).into_iter() { 413 | let nextstate = PathState { point: neighbour, cost: cost + costgrid[&neighbour] }; 414 | 415 | if nextstate.cost < dist[&neighbour] { 416 | 417 | } 418 | 419 | 420 | } 421 | 422 | 423 | } 424 | 425 | vec![] 426 | 427 | } 428 | */ 429 | 430 | 431 | 432 | #[derive(Eq,PartialEq)] 433 | struct PathState { 434 | point: Point, 435 | cost: u32 436 | } 437 | 438 | 439 | #[derive(Eq,PartialEq,Default,Clone)] 440 | pub struct RenderedTextCell { 441 | ///The background colour (R,G,B) 442 | pub background_colour: Option<(u8,u8,u8)>, 443 | ///The foreground colour (R,G,B) 444 | pub foreground_colour: Option<(u8,u8,u8)>, 445 | ///the glyph to render, the text should span only one cell (or at least consistently the same number for the entire grid) 446 | pub text: Option, 447 | } 448 | 449 | //TODO later: implement Add/AssignAdd to combine RenderedTextCells so we can combine different 450 | //layers 451 | 452 | impl fmt::Display for RenderedTextCell { 453 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 454 | let text: &str = if let Some(s) = self.text.as_ref() { 455 | s 456 | } else { 457 | " " 458 | }; 459 | if self.background_colour.is_some() || self.foreground_colour.is_some() { 460 | let mut style = ansi_term::Style::new(); 461 | if let Some((r,g,b)) = self.background_colour { 462 | style = style.on(ansi_term::Colour::RGB(r,g,b)); 463 | } 464 | if let Some((r,g,b)) = self.foreground_colour { 465 | style = style.fg(ansi_term::Colour::RGB(r,g,b)); 466 | } 467 | write!(f, "{}",style.paint(text)) 468 | } else { 469 | write!(f, "{}",text) 470 | } 471 | } 472 | } 473 | 474 | 475 | 476 | // The priority queue depends on `Ord`. (from: 477 | // https://doc.rust-lang.org/std/collections/binary_heap/index.html) 478 | // Explicitly implement the trait so the queue becomes a min-heap 479 | // instead of a max-heap. 480 | impl Ord for PathState where 481 | ScaleType: Integer + FromPrimitive + ToPrimitive + Bounded + Copy { 482 | 483 | fn cmp(&self, other: &PathState) -> Ordering { 484 | // Notice that the we flip the ordering on costs. 485 | // In case of a tie we compare positions - this step is necessary 486 | // to make implementations of `PartialEq` and `Ord` consistent. 487 | other.cost.cmp(&self.cost) 488 | .then_with(|| self.point.cmp(&other.point)) 489 | } 490 | } 491 | 492 | // `PartialOrd` needs to be implemented as well. 493 | impl PartialOrd for PathState where 494 | ScaleType: Integer + FromPrimitive + ToPrimitive + Bounded + Copy { 495 | 496 | fn partial_cmp(&self, other: &PathState) -> Option { 497 | Some(self.cmp(other)) 498 | } 499 | } 500 | 501 | 502 | ///Implementing the index ([]) operator for Grid 503 | impl Index<&Point> for Grid where 504 | ScaleType: Integer + FromPrimitive + ToPrimitive + Bounded + Copy, 505 | ValueType: Default + PartialEq + Clone { 506 | 507 | type Output = ValueType; 508 | 509 | fn index(&self, point: &Point) -> &Self::Output { 510 | >::get(self,point).expect("Out of bounds") 511 | } 512 | } 513 | 514 | 515 | 516 | impl<'a, ScaleType, ValueType> Iterator for GridIterator<'a, ScaleType,ValueType> where 517 | ScaleType: Integer + FromPrimitive + ToPrimitive + Bounded + Copy, 518 | ValueType: Default + PartialEq + Clone { 519 | 520 | type Item = (Point,&'a ValueType); 521 | 522 | fn next(&mut self) -> Option { 523 | if let Some(point) = self.current.next() { 524 | if let Some(value) = self.grid.get(&point) { 525 | Some( (point, value) ) 526 | } else { 527 | None 528 | } 529 | } else { 530 | None 531 | } 532 | } 533 | } 534 | 535 | 536 | -------------------------------------------------------------------------------- /src/heightgrid.rs: -------------------------------------------------------------------------------- 1 | use rand::{SeedableRng,Rng}; 2 | use rand_pcg::Pcg32; 3 | use std::cmp::{min,PartialEq,Eq}; 4 | use num::{Integer,Num,FromPrimitive,ToPrimitive,Bounded,range,CheckedAdd,CheckedSub}; 5 | 6 | use crate::common::{Distance,Direction,Volume}; 7 | use crate::point::Point; 8 | use crate::rectangle::Rectangle; 9 | use crate::grid::{Grid,GenericGrid,NumericGrid,RenderedTextCell}; 10 | 11 | #[derive(Debug,Default)] 12 | pub struct HeightGridProperties { 13 | ///number of iterations 14 | pub iterations: usize, 15 | } 16 | 17 | #[derive(Debug,Clone,Copy)] 18 | pub enum HeightRenderStyle { 19 | Simple, 20 | HeatMap, 21 | Terrain 22 | } 23 | 24 | pub trait HeightGrid where 25 | ScaleType: Integer + FromPrimitive + ToPrimitive + Bounded + Copy, 26 | ValueType: Num + FromPrimitive + ToPrimitive + PartialOrd + PartialEq + Bounded + CheckedAdd + CheckedSub + Copy + Default { 27 | 28 | fn generate(width: ScaleType, height: ScaleType, seed: u64, properties: HeightGridProperties) -> Grid; 29 | fn render(&self, renderstyle: HeightRenderStyle) -> Grid; 30 | fn rendercell(&self, point: &Point, min: ValueType, max: ValueType,renderstyle: HeightRenderStyle) -> RenderedTextCell; 31 | } 32 | 33 | impl HeightGrid for Grid where 34 | ScaleType: Integer + FromPrimitive + ToPrimitive + Bounded + Copy, 35 | ValueType: Num + FromPrimitive + ToPrimitive + PartialOrd + PartialEq + Bounded + CheckedAdd + CheckedSub + Copy + Default { 36 | 37 | fn generate(width: ScaleType, height: ScaleType, seed: u64, properties: HeightGridProperties) -> Grid { 38 | let mut rng = Pcg32::seed_from_u64(seed); 39 | let mut grid: Grid = Grid::new(width,height); 40 | for i in 0..properties.iterations { 41 | let rect: Rectangle = Rectangle::random(&mut rng, &grid.rectangle(), 42 | Some(ScaleType::one()), //minwidth 43 | Some(ScaleType::from_usize(grid.width_as_usize() / 5).expect("conversion error")), //maxwidth 44 | Some(ScaleType::one()), //minheight 45 | Some(ScaleType::from_usize(grid.height_as_usize() / 5).expect("conversion error")), //maxheight 46 | ); 47 | for point in rect.iter() { 48 | let cornercase: bool = (rect.width() >= ScaleType::from_u8(3).unwrap() && (point.x() == rect.topleft.x() || point.x() == rect.topright().x())) 49 | && (point.y() >= ScaleType::from_u8(3).unwrap() && (point.y() == rect.topleft.y() || point.y() == rect.bottomright.y())); 50 | if !cornercase { 51 | grid.inc(&point, ValueType::one()); 52 | } 53 | } 54 | } 55 | grid 56 | } 57 | 58 | 59 | fn render(&self,renderstyle: HeightRenderStyle) -> Grid { 60 | let mut renderedgrid: Grid = Grid::new(self.width(), self.height()); 61 | let min = self.min(); 62 | let max = self.max(); 63 | for (i, point) in self.rectangle().iter().enumerate() { 64 | renderedgrid.set(&point, HeightGrid::rendercell(self, &point, min, max, renderstyle) ); 65 | } 66 | renderedgrid 67 | } 68 | 69 | fn rendercell(&self, point: &Point, min: ValueType, max: ValueType,renderstyle: HeightRenderStyle) -> RenderedTextCell { 70 | let v = self[point].to_usize().unwrap(); 71 | let min = min.to_usize().unwrap(); 72 | let max = max.to_usize().unwrap(); 73 | let (r,g,b): (u8,u8,u8) = match renderstyle { 74 | HeightRenderStyle::Simple => { 75 | let colour: usize = (v - min) * (255/(max-min)); 76 | let colour: u8 = colour as u8; 77 | (colour,colour, colour) 78 | }, 79 | HeightRenderStyle::HeatMap => { 80 | //convert HSV (hue, saturation, value) to RGB, assuming saturation and value are 81 | //always max (1) 82 | let hue: f64 = 360.0 - ((v as f64 - min as f64) * (360.0/(max as f64 - min as f64))); 83 | let x: f64 = 1.0 - ((hue / 60.0) % 2.0 - 1.0).abs(); 84 | let x: u8 = (x * 255.0) as u8; 85 | match hue { 86 | _ if hue < 60.0 => (255, x , 0), 87 | _ if hue < 120.0 => (x, 255, 0), 88 | _ if hue < 180.0 => (0, 255, x), 89 | _ if hue < 240.0 => (0, x, 255), 90 | _ if hue < 300.0 => (x, 0, 255), 91 | _ => (x, 0, 255) 92 | } 93 | }, 94 | HeightRenderStyle::Terrain => { 95 | let level: usize = (v - min) * (100/(max-min)); 96 | match level { 97 | _ if level <= 15 => { 98 | //deep water - blue 99 | (0,0,100) 100 | }, 101 | _ if level <= 30 => { 102 | //shallow water - blue 103 | (0,0,200) 104 | }, 105 | _ if level <= 45 => { 106 | //terrain - green 107 | (0,133,0) 108 | }, 109 | _ if level <= 75 => { 110 | //terrain - darker green 111 | (0,79,0) 112 | }, 113 | _ if level <= 85 => { 114 | //terrain - green/gray 115 | (46,75,46) 116 | }, 117 | _ => { 118 | //snowtops 119 | (165,165,165) 120 | } 121 | } 122 | } 123 | }; 124 | RenderedTextCell { 125 | background_colour: Some((r,g,b)), 126 | foreground_colour: None, 127 | text: None, //defaults to space 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate rand; 2 | extern crate clap; 3 | extern crate num; 4 | extern crate ansi_term; 5 | 6 | pub mod grid; 7 | pub mod point; 8 | pub mod rectangle; 9 | pub mod common; 10 | pub mod pipegrid; 11 | pub mod heightgrid; 12 | pub mod roomgrid; 13 | 14 | use clap::{App,Arg}; 15 | use std::iter::Iterator; 16 | use std::thread; 17 | use std::time; 18 | 19 | use grid::Grid; 20 | use pipegrid::{PipeGrid,PipeGridProperties,PipeRenderStyle}; 21 | use heightgrid::{HeightGrid,HeightGridProperties,HeightRenderStyle}; 22 | use roomgrid::{RoomGrid,RoomGridProperties}; 23 | 24 | 25 | fn main() { 26 | let argmatches = App::new("mapgen") 27 | .version("0.1") 28 | .author("Maarten van Gompel (proycon) ") 29 | .about("Procedural map generation prototype") 30 | .arg(Arg::with_name("width") 31 | .help("width") 32 | .long("width") 33 | .short("w") 34 | .takes_value(true) 35 | .default_value("80") 36 | ) 37 | .arg(Arg::with_name("height") 38 | .help("height") 39 | .long("height") 40 | .short("h") 41 | .takes_value(true) 42 | .default_value("30") 43 | ) 44 | .arg(Arg::with_name("seed") 45 | .help("seed (0 = random seed)") 46 | .long("seed") 47 | .short("s") 48 | .takes_value(true) 49 | .default_value("0") 50 | ) 51 | .arg(Arg::with_name("loop") 52 | .help("Loop, keep generating random maps ever x milliseconds until the user aborts with control-C") 53 | .long("loop") 54 | .short("l") 55 | .takes_value(true) 56 | ) 57 | .arg(Arg::with_name("backboneseeds") 58 | .help("backboneseeds") 59 | .long("backboneseeds") 60 | .short("b") 61 | .takes_value(true) 62 | .default_value("20") 63 | ) 64 | .arg(Arg::with_name("regularseeds") 65 | .help("regularseeds") 66 | .long("regularseeds") 67 | .short("r") 68 | .takes_value(true) 69 | .default_value("40,40,60") 70 | ) 71 | .arg(Arg::with_name("interconnect") 72 | .help("(For pipe maps) Generate more interconnections between branches, resulting in fewer dead ends") 73 | .long("interconnect") 74 | .short("x") 75 | ) 76 | .arg(Arg::with_name("iterations") 77 | .help("(For height map) Iterations in generation") 78 | .long("iterations") 79 | .short("i") 80 | .default_value("90") 81 | ) 82 | .arg(Arg::with_name("rooms") 83 | .help("Number of rooms (for room map)") 84 | .long("rooms") 85 | .short("R") 86 | .default_value("6") 87 | ) 88 | .arg(Arg::with_name("style") 89 | .help("Rendering style. For pipes: thin (default), thick") 90 | .long("style") 91 | .short("y") 92 | .default_value("default") 93 | ) 94 | .arg(Arg::with_name("type") 95 | .help("type") 96 | .long("type") 97 | .short("t") 98 | .takes_value(true) 99 | .required(true) 100 | .default_value("pipes") 101 | ) 102 | .get_matches(); 103 | 104 | let mut looptime: u64 = 0; 105 | if argmatches.is_present("loop") { 106 | looptime = argmatches.value_of("loop").unwrap().parse::().expect("Invalid loop time"); 107 | } 108 | 109 | loop { 110 | 111 | let mut seed: u64 = argmatches.value_of("seed").unwrap().parse::().expect("Invalid seed"); 112 | if seed == 0 { 113 | seed = rand::random::(); 114 | } else { 115 | //looping makes no sense if we have a specified seed 116 | looptime = 0; 117 | } 118 | let width = argmatches.value_of("width").unwrap().parse::().expect("Invalid width") as usize; 119 | let height = argmatches.value_of("height").unwrap().parse::().expect("Invalid height") as usize; 120 | match argmatches.value_of("type").unwrap() { 121 | "pipes" => { 122 | let regularseeds: Option>= argmatches.value_of("regularseeds").map(|regularseeds: &str| { 123 | regularseeds.split_terminator(',').collect() 124 | }); 125 | let regularseeds: Vec = regularseeds.unwrap().iter().map(|x:&&str| { x.parse::().unwrap() } ).collect(); 126 | //using a construction: https://doc.rust-lang.org/book/ch19-03-advanced-traits.html 127 | // to construct the grid 128 | let grid: Grid = as PipeGrid>::generate(width as u16,height as u16, seed, PipeGridProperties { 129 | backboneseeds: argmatches.value_of("backboneseeds").unwrap().parse::().unwrap() as u16, 130 | regularseeds: regularseeds, 131 | interconnect: argmatches.is_present("interconnect"), 132 | }); 133 | println!("{}", PipeGrid::render(&grid, match argmatches.value_of("style").unwrap() { 134 | "thick" => PipeRenderStyle::Thick, 135 | _ => PipeRenderStyle::Thin 136 | })); 137 | }, 138 | "height" => { 139 | let grid: Grid = as HeightGrid>::generate(width as u16, height as u16, seed, HeightGridProperties { 140 | iterations: argmatches.value_of("iterations").unwrap().parse::().unwrap() as usize, 141 | }); 142 | println!("{}", HeightGrid::render(&grid, match argmatches.value_of("style").unwrap() { 143 | "heatmap" => HeightRenderStyle::HeatMap, 144 | "terrain" => HeightRenderStyle::Terrain, 145 | _ => HeightRenderStyle::Simple 146 | })); 147 | }, 148 | "rooms" => { 149 | let grid: Grid = as RoomGrid>::generate(width as u16, height as u16, seed, RoomGridProperties { 150 | rooms: argmatches.value_of("rooms").unwrap().parse::().unwrap() as usize, 151 | }); 152 | println!("{}", RoomGrid::render(&grid)); 153 | }, 154 | _ => { 155 | eprintln!("No such type"); 156 | break; 157 | } 158 | } 159 | if looptime > 0 { 160 | //escape sequence to clear screen 161 | print!("{}[2J", 27 as char); 162 | thread::sleep(time::Duration::from_millis(looptime)); 163 | } else { 164 | break; 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/pipegrid.rs: -------------------------------------------------------------------------------- 1 | use rand::{SeedableRng,Rng}; 2 | use rand_pcg::Pcg32; 3 | use std::cmp::{min,PartialEq,Eq}; 4 | use num::{Integer,Num,FromPrimitive,ToPrimitive,Bounded,range,CheckedAdd,CheckedSub}; 5 | 6 | use crate::common::{Distance,Direction,Volume}; 7 | use crate::point::Point; 8 | use crate::rectangle::Rectangle; 9 | use crate::grid::{Grid,GenericGrid,NumericGrid,RenderedTextCell}; 10 | 11 | #[derive(Debug,Default)] 12 | pub struct PipeGridProperties { 13 | ///initial backbone points 14 | pub backboneseeds: u16, 15 | 16 | ///amount of regular seeds to place, each element corresponds to an iteration 17 | pub regularseeds: Vec, 18 | 19 | ///prune dead-ends to a large extent by interconnecting them 20 | pub interconnect: bool, 21 | } 22 | 23 | #[derive(Debug,Clone,Copy)] 24 | pub enum PipeRenderStyle { 25 | Thin, 26 | Thick 27 | } 28 | 29 | pub trait PipeGrid where 30 | ScaleType: Integer + FromPrimitive + ToPrimitive + Bounded + Copy, 31 | ValueType: Num + FromPrimitive + ToPrimitive + PartialOrd + PartialEq + Bounded + CheckedAdd + CheckedSub + Copy + Default { 32 | 33 | fn generate(width: ScaleType, height: ScaleType, seed: u64, properties: PipeGridProperties) -> Grid; 34 | fn render(&self, renderstyle: PipeRenderStyle) -> Grid; 35 | fn rendercell(&self, point: &Point ,renderstyle: PipeRenderStyle) -> RenderedTextCell; 36 | } 37 | 38 | 39 | impl PipeGrid for Grid where 40 | ScaleType: Integer + FromPrimitive + ToPrimitive + Bounded + Copy, 41 | ValueType: Num + FromPrimitive + ToPrimitive + PartialOrd + PartialEq + Bounded + CheckedAdd + CheckedSub + Copy + Default { 42 | 43 | ///Generates the network (a planar graph), with a backbone 44 | fn generate(width: ScaleType, height: ScaleType, seed: u64, properties: PipeGridProperties) -> Grid { 45 | let mut rng = Pcg32::seed_from_u64(seed); 46 | let mut grid: Grid = Grid::new(width, height); 47 | 48 | let mut backboneseeds: Vec> = Vec::new(); 49 | //add initial backbone nodes 50 | for _ in 0..properties.backboneseeds { 51 | let point = Point::random(&mut rng, &grid.rectangle()); 52 | grid.set(&point, ValueType::one()); 53 | backboneseeds.push(point); 54 | } 55 | 56 | let mut processed: Vec = Vec::new(); 57 | //connect each backnode node to the nearest other 58 | for (i, point) in backboneseeds.iter().enumerate() { 59 | processed.push(i); 60 | 61 | //find the nearest unconnected other 62 | let mut mindistance: Option = None; 63 | let mut closest: Option = None; 64 | for (j, point2) in backboneseeds.iter().enumerate() { 65 | if !processed.contains(&j) { 66 | let distance = point.distance(point2); 67 | if mindistance.is_none() || distance < mindistance.unwrap() { 68 | mindistance = Some(distance); 69 | closest = Some(j); 70 | } 71 | } 72 | } 73 | 74 | //draw a random path 75 | if let Some(closest) = closest { 76 | let point2 = backboneseeds[closest]; 77 | grid.randompathto(&mut rng, point, &point2, ValueType::from_u8(2).unwrap()); 78 | } 79 | } 80 | 81 | //Add regular nodes (multiple iterations of a specific amount of seeds) 82 | for (iternr, regularseedgoal) in properties.regularseeds.iter().enumerate() { 83 | let mut regularseeds = 0; 84 | let height: ValueType = ValueType::from_usize(iternr).expect("Conversion error") + ValueType::from_u8(3).unwrap(); 85 | while regularseeds < *regularseedgoal { 86 | let point = Point::random(&mut rng, &grid.rectangle()); 87 | if grid[&point] == ValueType::zero() { 88 | regularseeds += 1; 89 | grid.set(&point,height); 90 | //find the closest backbone 91 | let mut mindistance: Option = None; 92 | let mut closest: Option> = None; 93 | for (point2, v) in grid.iter() { 94 | if *v > ValueType::zero() && *v < height { 95 | let distance: f64 = point.distance(&point2); 96 | if mindistance.is_none() || distance < mindistance.unwrap() { 97 | mindistance = Some(distance); 98 | closest = Some(point2); 99 | } 100 | } 101 | } 102 | // 103 | //draw a random path to the closest backbone 104 | if let Some(point2) = closest { 105 | grid.randompathto(&mut rng, &point, &point2, height+ValueType::one()); 106 | } 107 | } 108 | } 109 | } 110 | 111 | if properties.interconnect { 112 | //prune dead ends by creating more interconnections 113 | let mut deadends: Vec> = Vec::new(); 114 | let mut processed: Vec> = Vec::new(); 115 | //find all dead ends 116 | for (point,value) in grid.iter() { 117 | if *value > ValueType::from_u8(2).unwrap() && grid.countneighbours(&point) == 1 { 118 | deadends.push(point); 119 | } 120 | } 121 | 122 | for point in deadends.iter() { 123 | if !processed.contains(point) { 124 | //we find the closest other dead end (or former dead end) 125 | let mut mindistance: Option = None; 126 | let mut closest: Option> = None; 127 | for point2 in deadends.iter() { 128 | if point != point2 { 129 | let distance: f64 = point.distance(&point2); 130 | if mindistance.is_none() || distance < mindistance.unwrap() { 131 | mindistance = Some(distance); 132 | closest = Some(point2.clone()); 133 | } 134 | } 135 | } 136 | //draw a random path to the closest (former) dead end 137 | if let Some(closest) = closest { 138 | grid.randompathto(&mut rng, point, &closest, ValueType::from_u8(99).unwrap()); 139 | processed.push(closest); 140 | } 141 | } 142 | } 143 | } 144 | grid 145 | } 146 | 147 | fn render(&self,renderstyle: PipeRenderStyle) -> Grid { 148 | let mut renderedgrid: Grid = Grid::new(self.width(), self.height()); 149 | for (i, point) in self.rectangle().iter().enumerate() { 150 | renderedgrid.set(&point, PipeGrid::rendercell(self, &point, renderstyle) ); 151 | } 152 | renderedgrid 153 | } 154 | 155 | fn rendercell(&self, point: &Point, renderstyle: PipeRenderStyle) -> RenderedTextCell { 156 | let v = self[point]; 157 | let chr: char = if v == ValueType::zero() { 158 | ' ' 159 | } else { 160 | let (hasnorth, haseast, hassouth, haswest) = self.hasneighbours(point); 161 | let isbackbone = v <= ValueType::from_u8(2).unwrap(); 162 | match renderstyle { 163 | PipeRenderStyle::Thick => '█', 164 | PipeRenderStyle::Thin => { 165 | match (hasnorth, haseast, hassouth, haswest, isbackbone) { 166 | (true,true,true,true, false) => '┼', 167 | (true,true,true,true, true) => '╋', 168 | (true,true,true,false, false) => '├', 169 | (true,true,true,false, true) => '┣', 170 | (false,true,true,true, false) => '┬', 171 | (false,true,true,true, true) => '┳', 172 | (true,false,true,true, false) => '┤', 173 | (true,false,true,true, true) => '┫', 174 | (true,true,false,true, false) => '┴', 175 | (true,true,false,true, true) => '┻', 176 | (true,true,false,false, false) => '└', 177 | (true,true,false,false, true) => '┗', 178 | (true,false,true,false, false) => '│', 179 | (true,false,true,false, true) => '┃', 180 | (true,false,false,true, false) => '┘', 181 | (true,false,false,true, true) => '┛', 182 | (false,true,true,false, false) => '┌', 183 | (false,true,true,false, true) => '┏', 184 | (false,true,false,true, false) => '─', 185 | (false,true,false,true, true) => '━', 186 | (false,false,true,true, false) => '┐', 187 | (false,false,true,true, true) => '┓', 188 | (true,false,false,false, false) => '╵', 189 | (true,false,false,false, true) => '╹', 190 | (false,true,false,false, false) => '╶', 191 | (false,true,false,false, true) => '╺', 192 | (false,false,true,false, false) => '╷', 193 | (false,false,true,false, true) => '╻', 194 | (false,false,false,true, false) => '╴', 195 | (false,false,false,true, true) => '╸', 196 | _ => '?', 197 | } 198 | } 199 | } 200 | }; 201 | RenderedTextCell { 202 | background_colour: None, 203 | foreground_colour: None, 204 | text: Some(chr.to_string()) 205 | } 206 | } 207 | 208 | } 209 | -------------------------------------------------------------------------------- /src/point.rs: -------------------------------------------------------------------------------- 1 | use rand::{SeedableRng,Rng}; 2 | use rand_pcg::Pcg32; 3 | use num::{Integer,Num,FromPrimitive,ToPrimitive,range}; 4 | use std::ops::{Add,AddAssign}; 5 | use std::cmp::{min,PartialEq,Eq,Ord,Ordering}; 6 | use std::fmt; 7 | 8 | use crate::common::{Distance,Direction}; 9 | use crate::rectangle::Rectangle; 10 | 11 | ///A Point in an X,Y plane 12 | #[derive(Debug,Clone,Copy,PartialEq,Eq)] 13 | pub struct Point(pub ScaleType,pub ScaleType); 14 | 15 | ///A point in a 2D euclidian plane 16 | impl Point where 17 | ScaleType: Integer + FromPrimitive + ToPrimitive + Copy { 18 | 19 | pub fn new(x: ScaleType, y: ScaleType) -> Point { 20 | Point(x,y) 21 | } 22 | 23 | //conversion 24 | pub fn new_usize(x: usize, y: usize) -> Point { Point(ScaleType::from_usize(x).expect("Out of bounds"), ScaleType::from_usize(y).expect("Out of bounds")) } 25 | pub fn new64(x: u64, y: u64) -> Point { Point(ScaleType::from_u64(x).expect("Out of bounds"), ScaleType::from_u64(y).expect("Out of bounds")) } 26 | pub fn new32(x: u32, y: u32) -> Point { Point(ScaleType::from_u32(x).expect("Out of bounds"), ScaleType::from_u32(y).expect("Out of bounds")) } 27 | pub fn new16(x: u16, y: u16) -> Point { Point(ScaleType::from_u16(x).expect("Out of bounds"), ScaleType::from_u16(y).expect("Out of bounds")) } 28 | pub fn new8(x: u8, y: u8) -> Point { Point(ScaleType::from_u8(x).expect("Out of bounds"), ScaleType::from_u8(y).expect("Out of bounds")) } 29 | 30 | //Generate a random point within the specified rectangular bound 31 | pub fn random(rng: &mut Pcg32, bounds: &Rectangle) -> Point { 32 | Point::new64( 33 | rng.gen_range(bounds.topleft.xs(),bounds.bottomright.xs() + 1) as u64, 34 | rng.gen_range(bounds.topleft.ys(),bounds.bottomright.ys() + 1) as u64 35 | ) 36 | } 37 | 38 | 39 | pub fn x(&self) -> ScaleType { self.0 } 40 | pub fn y(&self) -> ScaleType { self.1 } 41 | 42 | pub fn rectangle(&self, width: ScaleType, height: ScaleType) -> Rectangle { 43 | Rectangle::new_dims(self.x(), self.y(), width, height) 44 | } 45 | 46 | pub fn square(&self, size: ScaleType) -> Rectangle { 47 | Rectangle::new_dims(self.x(), self.y(), size, size) 48 | } 49 | 50 | pub fn square1(&self) -> Rectangle { 51 | Rectangle::new_dims(self.x(), self.y(), ScaleType::one(), ScaleType::one()) 52 | } 53 | 54 | //conversion 55 | pub fn xs(&self) -> usize { self.0.to_usize().expect("Out of bounds") } 56 | pub fn ys(&self) -> usize { self.1.to_usize().expect("Out of bounds") } 57 | pub fn x64(&self) -> u64 { self.0.to_u64().expect("Out of bounds") } 58 | pub fn y64(&self) -> u64 { self.1.to_u64().expect("Out of bounds") } 59 | pub fn x32(&self) -> u32 { self.0.to_u32().expect("Out of bounds") } 60 | pub fn y32(&self) -> u32 { self.1.to_u32().expect("Out of bounds") } 61 | pub fn x16(&self) -> u16 { self.0.to_u16().expect("Out of bounds") } 62 | pub fn y16(&self) -> u16 { self.1.to_u16().expect("Out of bounds") } 63 | pub fn x8(&self) -> u8 { self.0.to_u8().expect("Out of bounds") } 64 | pub fn y8(&self) -> u8 { self.1.to_u8().expect("Out of bounds") } 65 | 66 | 67 | pub fn neighbour(&self, direction: Direction, width: Option, height: Option) -> Option> { 68 | match direction { 69 | Direction::North => { if self.y() == ScaleType::zero() { 70 | None 71 | } else { 72 | Some(Point(self.x(), self.y() - ScaleType::one())) 73 | } }, 74 | Direction::East => { if width.is_some() && self.x() == width.unwrap() - ScaleType::one() { 75 | None 76 | } else { 77 | Some(Point(self.x() + ScaleType::one(), self.y() )) 78 | } }, 79 | Direction::South => { if height.is_some() && self.y() == height.unwrap() - ScaleType::one() { 80 | None 81 | } else { 82 | Some(Point(self.x(), self.y() + ScaleType::one())) 83 | } }, 84 | Direction::West => { if self.x() == ScaleType::zero() { 85 | None 86 | } else { 87 | Some(Point(self.x() - ScaleType::one(), self.y())) 88 | } }, 89 | } 90 | } 91 | 92 | //aliases for neighbour 93 | pub fn north(&self) -> Option> { self.neighbour(Direction::North, None, None) } 94 | pub fn west(&self) -> Option> { self.neighbour(Direction::West, None, None) } 95 | pub fn south(&self, height: Option) -> Option> { self.neighbour(Direction::West, None, height) } 96 | pub fn east(&self, width: Option) -> Option> { self.neighbour(Direction::East, width, None) } 97 | 98 | pub fn set(&mut self, x: ScaleType, y: ScaleType) { 99 | self.0 = x; 100 | self.1 = y; 101 | } 102 | 103 | 104 | } 105 | 106 | impl Distance for Point where 107 | ScaleType: Integer + FromPrimitive + ToPrimitive + Copy { 108 | 109 | ///A simple euclidian distance function 110 | fn distance(&self, other: &Point) -> f64 { 111 | let x = self.x64(); 112 | let y = self.y64(); 113 | let x2 = other.x64(); 114 | let y2 = other.y64(); 115 | let distx: f64 = (x2 as f64 - x as f64).abs(); 116 | let disty: f64 = (y2 as f64 - y as f64).abs(); 117 | let distance: f64 = (distx.powf(2.0) + disty.powf(2.0)).sqrt(); 118 | distance 119 | } 120 | } 121 | 122 | impl Add for Point where 123 | ScaleType: Integer + FromPrimitive + ToPrimitive + Copy { 124 | 125 | type Output = Point; 126 | 127 | fn add(self, other: Point) -> Point { 128 | Point(self.x() + other.x(), self.y() + other.y()) 129 | } 130 | } 131 | 132 | impl AddAssign for Point where 133 | ScaleType: Integer + FromPrimitive + ToPrimitive + Copy { 134 | 135 | fn add_assign(&mut self, other: Point) { 136 | self.0 = self.0 + other.x(); 137 | self.1 = self.1 + other.y(); 138 | } 139 | } 140 | 141 | impl fmt::Display for Point where 142 | ScaleType: Integer + FromPrimitive + ToPrimitive + Copy { 143 | 144 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 145 | write!(f, "({},{})", self.x64(), self.y64()) 146 | } 147 | } 148 | 149 | impl PartialOrd for Point where 150 | ScaleType: Integer + FromPrimitive + ToPrimitive + Copy { 151 | 152 | fn partial_cmp(&self, other: &Self) -> Option { 153 | Some(self.cmp(other)) 154 | } 155 | 156 | } 157 | 158 | impl Ord for Point where 159 | ScaleType: Integer + FromPrimitive + ToPrimitive + Copy { 160 | 161 | fn cmp(&self, other: &Self) -> Ordering { 162 | if self.y() < other.y() { 163 | Ordering::Less 164 | } else if self.y() > other.y() { 165 | Ordering::Greater 166 | } else if self.x() < other.x() { 167 | Ordering::Less 168 | } else if self.x() > other.x() { 169 | Ordering::Greater 170 | } else { 171 | Ordering::Equal 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/rectangle.rs: -------------------------------------------------------------------------------- 1 | use rand::{SeedableRng,Rng}; 2 | use rand_pcg::Pcg32; 3 | use clap::{App,Arg}; 4 | use num::{Integer,Num,FromPrimitive,ToPrimitive,range}; 5 | use std::ops::Index; 6 | use std::cmp::{min,PartialEq,Eq}; 7 | use std::fmt; 8 | use std::iter::Iterator; 9 | 10 | use crate::common::{Distance,Direction,Volume,fmin}; 11 | use crate::point::Point; 12 | 13 | 14 | #[derive(Debug,Clone,Copy,PartialEq,Eq)] 15 | pub struct Rectangle { 16 | pub topleft: Point, 17 | pub bottomright: Point, 18 | } 19 | 20 | ///An iterator over all points in the rectangle 21 | pub struct RectIterator { 22 | rectangle: Rectangle, 23 | current: Option>, //will be None at instantiation 24 | } 25 | 26 | ///A rectangle in a 2D euclidian plane 27 | impl Rectangle where 28 | ScaleType: Integer + FromPrimitive + ToPrimitive + Copy { 29 | 30 | pub fn new(topleft: &Point, bottomright: &Point) -> Rectangle { 31 | Rectangle { 32 | topleft: topleft.clone(), 33 | bottomright: bottomright.clone() 34 | } 35 | } 36 | 37 | pub fn topright(&self) -> Point { 38 | Point(self.bottomright.x(), self.topleft.y()) 39 | } 40 | 41 | pub fn bottomleft(&self) -> Point { 42 | Point(self.topleft.x(), self.bottomright.y()) 43 | } 44 | 45 | pub fn left(&self) -> ScaleType { 46 | self.topleft.x() 47 | } 48 | 49 | pub fn right(&self) -> ScaleType { 50 | self.bottomright.x() 51 | } 52 | 53 | pub fn top(&self) -> ScaleType { 54 | self.topleft.y() 55 | } 56 | 57 | pub fn bottom(&self) -> ScaleType { 58 | self.bottomright.y() 59 | } 60 | 61 | ///Return a random point in the rectangle 62 | pub fn randompoint(&self, mut rng: &mut Pcg32) -> Point { 63 | return Point::random(&mut rng, &self); 64 | } 65 | 66 | pub fn new_dims(x: ScaleType, y: ScaleType, width: ScaleType, height: ScaleType) -> Rectangle { 67 | Rectangle { 68 | topleft: Point(x,y), 69 | bottomright: Point(x+width-ScaleType::one(),y+height-ScaleType::one()) 70 | } 71 | } 72 | // 73 | ///Generate a random rectangle within the specified rectangular bound 74 | pub fn random(rng: &mut Pcg32, bounds: &Rectangle, minwidth: Option, maxwidth: Option, minheight: Option, maxheight: Option) -> Rectangle { 75 | let minwidth = minwidth.unwrap_or(ScaleType::one()).to_usize().unwrap(); 76 | let maxwidth = maxwidth.unwrap_or(bounds.width()).to_usize().unwrap(); 77 | let minheight = minheight.unwrap_or(ScaleType::one()).to_usize().unwrap(); 78 | let maxheight = maxheight.unwrap_or(bounds.height()).to_usize().unwrap(); 79 | let topleft = Point::new64( 80 | rng.gen_range(bounds.topleft.xs(), bounds.bottomright.xs() + 1 - minwidth) as u64, 81 | rng.gen_range(bounds.topleft.ys(), bounds.bottomright.ys() + 1 - minheight) as u64 82 | ); 83 | let bottomright = Point::new64( 84 | rng.gen_range(topleft.xs() + minwidth, min(topleft.xs() + maxwidth, bounds.bottomright.xs() + 1)) as u64, 85 | rng.gen_range(topleft.ys() + minheight, min(topleft.ys() + maxheight, bounds.bottomright.ys() + 1) )as u64 86 | ); 87 | Rectangle { 88 | topleft: topleft, 89 | bottomright: bottomright, 90 | } 91 | } 92 | 93 | ///Iterate over all points in the rectangle 94 | pub fn iter(&self) -> RectIterator { 95 | RectIterator { 96 | rectangle: self.clone(), 97 | current: None, 98 | } 99 | } 100 | } 101 | 102 | impl Volume for Rectangle where 103 | ScaleType: Integer + FromPrimitive + ToPrimitive + Copy { 104 | 105 | fn width(&self) -> ScaleType { 106 | self.bottomright.x() - self.topleft.x() + ScaleType::one() 107 | } 108 | 109 | fn height(&self) -> ScaleType { 110 | self.bottomright.y() - self.topleft.y() + ScaleType::one() 111 | } 112 | 113 | fn intersects(&self, other: &Self) -> bool { 114 | self.bottomright.x() >= other.topleft.x() && self.topleft.x() <= other.topright().x() && 115 | self.bottomright.y() >= other.topleft.y() && self.topleft.y() <= other.bottomright.y() 116 | } 117 | } 118 | 119 | impl Distance for Rectangle where 120 | ScaleType: Integer + FromPrimitive + ToPrimitive + Copy { 121 | 122 | ///Computes the distance between two boxes, the distance is the shortest distance between a 123 | ///corner of box A and a corner of box B 124 | fn distance(&self, other: &Self) -> f64 { 125 | let mut d: f64 = self.topleft.distance(&other.topright()); 126 | d = fmin(d, self.topleft.distance(&other.bottomright)); 127 | d = fmin(d, self.topleft.distance(&other.bottomleft() )); 128 | 129 | d = fmin(d, self.topright().distance(&other.topleft )); 130 | d = fmin(d, self.topright().distance(&other.bottomright ) ); 131 | d = fmin(d, self.topright().distance(&other.bottomleft() ) ); 132 | 133 | d = fmin(d, self.bottomright.distance(&other.topleft) ); 134 | d = fmin(d, self.bottomright.distance(&other.topright() )); 135 | d = fmin(d, self.bottomright.distance(&other.bottomleft() )); 136 | 137 | d = fmin(d, self.bottomleft().distance(&other.topleft) ); 138 | d = fmin(d, self.bottomleft().distance(&other.bottomright )); 139 | d = fmin(d, self.bottomleft().distance(&other.topright() ) ); 140 | 141 | d 142 | } 143 | } 144 | 145 | impl Iterator for RectIterator where 146 | ScaleType: Integer + FromPrimitive + ToPrimitive + Copy { 147 | 148 | type Item = Point; 149 | 150 | fn next(&mut self) -> Option { 151 | if let Some(mut current) = self.current { 152 | current = current.east(None).expect("Iterator out of bounds"); 153 | if current.x() > self.rectangle.bottomright.x() { 154 | //next row 155 | current.set( self.rectangle.topleft.x(), current.y() + ScaleType::one()); 156 | if current.y() > self.rectangle.bottomright.y() { 157 | //out of bounds, stop condition 158 | self.current = None 159 | } else { 160 | self.current = Some(current); 161 | } 162 | } else { 163 | self.current = Some(current); 164 | } 165 | } else { 166 | self.current = Some(self.rectangle.topleft.clone()); 167 | }; 168 | self.current 169 | } 170 | } 171 | 172 | -------------------------------------------------------------------------------- /src/roomgrid.rs: -------------------------------------------------------------------------------- 1 | use rand::{SeedableRng,Rng}; 2 | use rand_pcg::Pcg32; 3 | use std::cmp::{min,max,PartialEq,Eq}; 4 | use num::{Integer,Num,FromPrimitive,ToPrimitive,Bounded,range,CheckedAdd,CheckedSub}; 5 | 6 | use crate::common::{Distance,Direction,Volume}; 7 | use crate::point::Point; 8 | use crate::rectangle::Rectangle; 9 | use crate::grid::{Grid,GenericGrid,NumericGrid,RenderedTextCell}; 10 | 11 | pub struct RoomGridProperties { 12 | pub rooms: usize, 13 | } 14 | 15 | pub trait RoomGrid where 16 | ScaleType: Integer + FromPrimitive + ToPrimitive + Bounded + Copy, 17 | ValueType: Num + FromPrimitive + ToPrimitive + PartialOrd + PartialEq + Bounded + CheckedAdd + CheckedSub + Copy + Default { 18 | 19 | fn generate(width: ScaleType, height: ScaleType, seed: u64, properties: RoomGridProperties) -> Grid; 20 | fn render(&self) -> Grid; 21 | fn rendercell(&self, point: &Point) -> RenderedTextCell; 22 | } 23 | 24 | impl RoomGrid for Grid where 25 | ScaleType: Integer + FromPrimitive + ToPrimitive + Bounded + Copy, 26 | ValueType: Num + FromPrimitive + ToPrimitive + PartialOrd + PartialEq + Bounded + CheckedAdd + CheckedSub + Copy + Default { 27 | 28 | fn generate(width: ScaleType, height: ScaleType, seed: u64, properties: RoomGridProperties) -> Grid { 29 | let mut rng = Pcg32::seed_from_u64(seed); 30 | let mut grid: Grid = Grid::new(width,height); 31 | let mut rooms: Vec> = Vec::new(); //left,top,width,height 32 | let mut tries = 0; 33 | while rooms.len() < properties.rooms && tries < 100 { //we give adding rooms when we fail after 100 tries 34 | let room: Rectangle = Rectangle::random(&mut rng, &grid.rectangle(), 35 | Some(ScaleType::from_u8(3).expect("conversion error")), //minwidth 36 | Some(ScaleType::from_usize(grid.width_as_usize() / 4).expect("conversion error")), //maxwidth 37 | Some(ScaleType::from_u8(3).expect("conversion error")), //minheight 38 | Some(ScaleType::from_usize(grid.height_as_usize() / 4).expect("conversion error")), //maxheight 39 | ); 40 | 41 | //the room may not overlap with others 42 | let mut overlaps = false; 43 | for room2 in rooms.iter() { 44 | if room.intersects(room2) { 45 | overlaps = true; 46 | break; 47 | } 48 | } 49 | if overlaps { 50 | tries += 1; 51 | continue; 52 | } 53 | 54 | for point in room.iter() { 55 | grid.inc(&point, ValueType::one()); 56 | } 57 | tries = 0; 58 | 59 | //we add the current room AFTER the following block: 60 | // 61 | if !rooms.is_empty() { 62 | //connect the room with the closest other room 63 | let mut mindistance: Option = None; 64 | let mut closest: Option = None; 65 | for (i, room2) in rooms.iter().enumerate() { 66 | let distance: f64 = room.distance(&room2); 67 | if mindistance.is_none() || distance < mindistance.unwrap() { 68 | mindistance = Some(distance); 69 | closest = Some(i); 70 | } 71 | } 72 | 73 | if let Some(index) = closest { 74 | let room2 = rooms.get(index).unwrap(); 75 | let mut corridor_h: Option = None; 76 | let mut corridor_v: Option = None; 77 | //can we do a horizontal corridor? 78 | if room.top() <= room2.bottom() && room.bottom() >= room2.top() { 79 | //horizontal corridor 80 | let corridor_h_min = max(room.top(), room2.top()); 81 | let corridor_h_max = min(room.bottom()+ScaleType::one(), room2.bottom()+ScaleType::one()); 82 | //eprintln!("H1: {}-{}", corridor_h_min.to_usize().unwrap(), corridor_h_max.to_usize().unwrap()); 83 | corridor_h = if corridor_h_min == corridor_h_max { 84 | Some(corridor_h_min) 85 | } else { 86 | Some(ScaleType::from_usize(rng.gen_range( corridor_h_min.to_usize().unwrap() , corridor_h_max.to_usize().unwrap() )).expect("Unable to compute corridor H")) 87 | }; 88 | } else if room.left() <= room2.right() && room.right() >= room2.left() { 89 | //vertical corridor 90 | let corridor_v_min = max(room.left(), room2.left()); 91 | let corridor_v_max = min(room.right()+ScaleType::one(), room2.right()+ScaleType::one()); 92 | //eprintln!("H1: {}-{}", corridor_v_min.to_usize().unwrap(), corridor_v_max.to_usize().unwrap()); 93 | corridor_v = if corridor_v_min == corridor_v_max { 94 | Some(corridor_v_min) 95 | } else { 96 | Some(ScaleType::from_usize(rng.gen_range( corridor_v_min.to_usize().unwrap() , corridor_v_max.to_usize().unwrap() )).expect("Unable to compute corridor H")) 97 | }; 98 | } 99 | if let Some(corridor_h) = corridor_h { 100 | let (begin_x, end_x) = if room.left() < room2.left() { 101 | (room.right(), room2.left()) 102 | } else { 103 | (room2.right(), room.left()) 104 | }; 105 | for x in range(begin_x, end_x) { 106 | grid.set(&Point(x,corridor_h), ValueType::one()); 107 | } 108 | } else if let Some(corridor_v) = corridor_v { 109 | let (begin_y, end_y) = if room.top() < room2.top() { 110 | (room.bottom(), room2.top()) 111 | } else { 112 | (room2.bottom(), room.top()) 113 | }; 114 | for y in range(begin_y, end_y) { 115 | grid.set(&Point(corridor_v,y), ValueType::one()); 116 | } 117 | } else { 118 | //cornered corridors 119 | let from: Point = room.randompoint(&mut rng); 120 | let to: Point = room2.randompoint(&mut rng); 121 | grid.rectpathto(&mut rng, &from, &to, ValueType::one()); 122 | } 123 | } 124 | } 125 | 126 | rooms.push(room); 127 | } 128 | 129 | grid 130 | } 131 | 132 | fn render(&self) -> Grid { 133 | let mut renderedgrid: Grid = Grid::new(self.width(), self.height()); 134 | for (i, point) in self.rectangle().iter().enumerate() { 135 | renderedgrid.set(&point, RoomGrid::rendercell(self, &point) ); 136 | } 137 | renderedgrid 138 | } 139 | 140 | fn rendercell(&self, point: &Point) -> RenderedTextCell { 141 | if self[&point] != ValueType::zero() { 142 | RenderedTextCell { 143 | background_colour: Some((127,127,127)), 144 | foreground_colour: None, 145 | text: None 146 | } 147 | } else { 148 | RenderedTextCell { 149 | background_colour: Some((0,0,0)), 150 | foreground_colour: None, 151 | text: None 152 | } 153 | } 154 | } 155 | } 156 | --------------------------------------------------------------------------------