diff --git a/vendored/ansi-terminal-game-1.8.0.0/COPYING b/vendored/ansi-terminal-game-1.8.0.0/COPYING new file mode 100644 index 0000000..45644ff --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/vendored/ansi-terminal-game-1.8.0.0/NEWS b/vendored/ansi-terminal-game-1.8.0.0/NEWS new file mode 100644 index 0000000..fee7020 --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/NEWS @@ -0,0 +1,275 @@ +1.8.0.0 +------- + +- Fixed testing facilities `recordGame`, `testGame`, `narrateGame` and + similar functions. `testGame` in particular is able to precisely + emulate recorded environment (so if your game has a bug only at a + specific size, `testGame` will now catch it). + Check `cabal run -f alone-playback examples ` to see a replay in action + and `test/Terminal/Game/Layer/ImperativeSpec.hs` for pure test ideas. +- Added information on how to have an hot-reload mode, albeit only for + non-interactive game replays. Check `example/MainHotReload.hs` if + interested. +- Added a new exception, `DisplayTooSmall`, which expands gracefully to + a “please resize your terminal” message to the player if uncaught. + Nothing changes if you do not already use `asserTermDims`. +- `assertTermDims` is now curries (`Width -> Height -> IO ()` instead + of `Dimensions -> IO ()`) to better fit the rest of the API. +- Modified behaviour of functions `vcat`, `hcat`, `stringPlane`, + `stringPlaneTrans`. They will not error on empty list, rather return + a transparent, 1×1 plane. +- Changed licence and changes files to COPYING and NEWS. + +1.7.0.0 +------- + +- After some feedback from library users, I decided to eliminate + `simpleGame` from the API. + To reiterate hte migration guide, if your type was: + + Game 80 24 13 initState logicFun drawFun quitFun + -- or + -- simpleGame (80, 24) 13 initState logicFun drawFun quitFun + + You just need to modify it like this: + + Game 13 initState + (const logicFun) + (\e s -> centerFull e $ drawFun s) + quitFun + -- notice how we lost `80 24`. You can still have a screen size + -- check with `assertTermDims`, as described below. +- Added `blankPlaneFull` and `centerFull` convenience functions (to work + with GEnv terminal dimensions). +- Added assertTermDims, a quick way to check your user terminal is big + enough at the start of the game. +- minimal blitting optimisation (you should be able to see a 1–2 + FPS improvement). +- improved documentation on various functions. + +1.6.0.2 +------- + +- lun 15 nov 2021, 02:21:08 +- more doc tweaking + +1.6.0.1 +------- + +- released lun 15 nov 2021, 00:35:41 +- minor documentation / spelling fixes + +1.6.0.0 +------- + +Summary and tl;dr migration guide: +- This version introduces a breaking changes in the main way to make + a `Game`. I will detail the changes below, but first a three-lines + migration guide: + the only thing you should have to do is to replace your `Game` + data constructor with `simpleGame` smart constructor, and substitute + the first to `c` `r` arguments (col/row) with a `(c, r)` tuple. + And of course, if you are interested in displaying FPS and adapt to + screen size modifications at game-time (“liquid” layout), read along! + +Changes: +- This version introduces GEnv, a structure that exposes current frame + rate (in FPS) and current terminal size (in Width, Height). +- `GEnv` is added as a parameter to logic and draw functions, which + now have these signatures: + gLogicFunction :: GEnv -> s -> Event -> slightly + gDrawFunction :: GEnv -> s -> plane +- If you do not want to dabble with GEnv, you can still use `simpleGame` + smart constructor, which mimicks the old `Game`. `simpleGame` has some + nice defaults: + - if the terminal is too small it will ask the player to resize it + (even in the middle of the game), blocking any input; + - if the terminal is bigger, it will paste `Plane` in the middle + of the screen. +- For this reason, `DisplayTooSmall` exception exists no more. +- the new `Game` does not have those defaults, but allows you to get + creative with screen resizes, e.g. accomodating as much gameworld + as possible etc. Check `cabal run -f examples balls` and resize the + screen to see it in action. +- Minor change: I have introduced a `Dimensions` alias for + `(Width, Height)`. + +Future work: +- these changes lay the path for an even more general `Game` type, + adding effects like reading form a game configuration, writing to it + etc. + I would like to have these wrapped in a pure interface (maybe à la + Response/Request? Maybe callbacks?) and for sure want them to be + composable with current test scaffolding (testGame, + narrateGame, etc.). It will not be easy to design; if are reading + this and have any suggestion, please write to me. + +Released dom 14 nov 2021, 20:25:19 + +1.5.0.0 +------- + +- `timers-tick` has released a new version: all timers function (creaTimer, + creaBoolTimer, creaTimerLoop, creaBoolTimerLoop, creaAnimation, + creaLoopAnimation, ticks) are slightly more robust now (will `error` + on nonsenical arguments, e.g. frame duration <1). + This should not impact any of your current projects, it just makes + catching bugs easier. +- Removed `getFrames` from Animation interface. +- Updated `Random` interface to fit the new `random`. This is a breaking + change but it should be easy to fix by updating your `Random` constraints + to `UniformRange`. + Be mindful that `recordGame` could play slightly differently, as the + update function for the StdGen in `random` has changed. +- Removed `getRandomList` from Random interface. +- Added `pickRandom` to Random interface. +- Removed unuseful `creaStaticAnimation` from Animation interface. +- Released mar 9 nov 2021, 15:56:14. + +1.4.0.0 +------- + +- Fixed an annoying bug that made a game run slower than expected on + low TPS. Now if you select 5 ticks per second, you can rest assured + that after 50 ticks, 5 seconds have elapsed. +- Renamed `FPS` to `TPS` (ticks per second); highlight logic speed is + constant timewise on all machines, while FPS might be different on + differently efficient terminals. + This will allow in future releases to provide a function to easily + calculate actual FPS of the game. +- Added alternative origin combinators `%^>`, `%.<`, `%.>`; they are + useful when you want to — e.g. — «paste a plane one row from + bottom-right corner». + +1.3.0.0 +------- + +- `displaySize` and `playGame`/`playGameS` now throw an exception + (of type `ATGException`) instead of `error`ing. These exeptions are + `CannotGetDisplaySize` and `DisplayTooSmall`; they are synchronous, + for easier catching. (requested by sm) +- Released sab 16 ott 2021, 21:09:22 + +1.2.1.0 +------- + +- Fixed textBox, textBoxHyphen bug (boxes were not transparent, contrary + to what stated in docs) (reported by sm). +- Released lun 11 ott 2021, 22:29:40 + +1.2.0.0 +------- + +- Added textBoxHyphen and textBoxHyphenLiquid and a handful of `Hypenator`s. + This will allow you to have autohyphenation in textboxes. Compare: + (normal textbox) (hyphenated textbox) + Rimasi un po’ a meditare nel buio Rimasi un po’ a meditare nel buio + velato appena dal barlume azzurrino velato appena dal barlume azzurrino + del fornello a gas, su cui del fornello a gas, su cui sobbol- + sobbollliva quieta la pentola. liva quieta la pentola. +- Switched `Width`, `Height`, `Row`, `Col` from `Integer` to `Int`. + This is unfortunate, but will make playing with `base` simpler. I will + switch it back once `Prelude` handles both integers appropriately + or exports the relevant function. (request by sm) +- Changed signature for `box`, `textBox` and `textBoxLiquid`. Now + width/height parameters come *before* the character/string. E.g.: + textBoxLiquid :: String -> Width -> Plane -- this was before + textBoxLiquid :: Width -> String -> Plane -- this is now + This felt more ergonomic while writing games. +- `paperPlane` is now `planePaper` (to respect SVO order) + +1.1.1.0 +------- + +- Added (***) (centre blit) (request by sm) +- Released gio 30 set 2021, 12:29:22 + +1.1.0.0 +------- + +- Added Plane justapoxition functions (===, |||, vcat, hcat). +- Added `word` and and `textBoxLiquid` drawing functions. +- Added `subPlane`, `displaySize` Plane functions. +- Removed unused `trimPlane`. +- Sanitized non-ASCII chars on Win32 console. +- Wed 03 Feb 2021 18:41:20 CET + +1.0.0.0 +------- + +- Milestone release. +- Beefed up documentation. +- Released Sun 08 Dec 2019 04:19:33 CET + +0.7.2.0 +------- + +- Fixed 0.7.1.0 unbumped dependency. +- Released Fri 22 Nov 2019 16:51:25 CET + +0.7.1.0 +------- + +- Fixed 0.7.0.0 (deprecated) interface. +- Released Fri 22 Nov 2019 14:51:40 CET + +0.7.0.0 +------- + +- Simplified Animation interface (breaking changes). +- Added `creaLoopAnimation` and `creaStaticAnimation`. +- Released Fri 22 Nov 2019 14:40:44 CET + +0.6.1.0 +------- + +- Reworked Timers/Animations interface and documentation. +- Added `lapse` (for Timers/Animations). +- Released Fri 22 Nov 2019 01:03:37 CET + +0.6.0.1 +------- + +- Add public repo (requested by sm). +- Released Tue 19 Nov 2019 22:38:34 CET + +0.6.0.0 +------- + +- Add random generation functions. +- Released Sun 10 Nov 2019 13:44:32 CET + +0.5.0.0 +------- + +- Add `setupGame` to setup games before playtesting (skip menus, etc.). +- Fixed screen corruption on Windows. +- Released Fri 08 Nov 2019 13:52:39 CET + +0.4.0.0 +------- + +- Exposed new functions in API. +- Greatly improved haddock documentation. +- Released Tue 25 Jun 2019 16:08:53 CEST + +0.2.1.0 +------- + +- Improved haddock documentation a bit. +- Cleanup runs regardless of exception. +- Released on Sun 18 Mar 2018 03:04:07 CET. + +0.2.0.0 +------- + +- Added dependencies constraints. +- Removed internal module. +- Fixed changelog. +- Released on Fri 16 Mar 2018 00:42:41 CET. + +0.1.0.0 +------- + +- Initial release. +- Released on Fri 16 Mar 2018 00:33:18 CET. diff --git a/vendored/ansi-terminal-game-1.8.0.0/README b/vendored/ansi-terminal-game-1.8.0.0/README new file mode 100644 index 0000000..e300fc8 --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/README @@ -0,0 +1,46 @@ +================== +ansi-terminal-game +================== + +`ansi-terminal-game` is a library for creating games in a terminal setting. + +Goals +----- + +- be cross platform (linux/win/mac). If you plan to have your executable + unix only, I invite you to check brick [1] or other, more expressive + libraries. +- be simple: no curses/ncurses/pdcurses/etc. dependencies, all + functionality built on a standard input / ANSI terminal base. + +[1] http://hackage.haskell.org/package/brick + +Learn +----- + +- run the basic example with `cabal new-run -f examples alone`; +- check the source in `examples/Alone.hs`; +- open the 'Terminal.Game' haddock documentation (start reading from + `Data.Game`). + +A full game can be found at: + + http://www.ariis.it/static/articles/venzone/page.html + +Other games made with a-t-g +--------------------------- + +- caverunner: + https://github.com/simonmichael/games/blob/main/caverunner/caverunner.hs +- pigafetta: http://www.ariis.it/link/repos/pigafetta/ +- avoidance: https://sabadev.xyz/avoidance_game + +If you want yours to be added to this list, write to me. + +Contact +------- + +For any feedback or report, contact me at: + + http://ariis.it/static/articles/mail/page.html + diff --git a/vendored/ansi-terminal-game-1.8.0.0/Setup.hs b/vendored/ansi-terminal-game-1.8.0.0/Setup.hs new file mode 100644 index 0000000..9a994af --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/vendored/ansi-terminal-game-1.8.0.0/ansi-terminal-game.cabal b/vendored/ansi-terminal-game-1.8.0.0/ansi-terminal-game.cabal new file mode 100644 index 0000000..ae16d91 --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/ansi-terminal-game.cabal @@ -0,0 +1,188 @@ +name: ansi-terminal-game +version: 1.8.0.0 +synopsis: sdl-like functions for terminal applications, based on + ansi-terminal +description: Library which aims to replicate standard 2d game + functions (blit, ticks, timers, etc.) in a terminal + setting; features double buffering to optimise + performance. + Aims to be cross compatible (based on "ansi-terminal", + no unix-only dependencies), practical. + See @examples@ folder for some minimal programs. A + full game: . +homepage: http://www.ariis.it/static/articles/ansi-terminal-game/page.html +license: GPL-3 +license-file: COPYING +author: Francesco Ariis +maintainer: fa-ml@ariis.it +copyright: © 2017-2021 Francesco Ariis +category: Game +build-type: Simple +extra-source-files: README, + NEWS, + test/records/alone-record-test.gr + test/records/balls-dims.gr + test/records/balls-slow.gr +cabal-version: >=1.10 + +flag examples + description: builds examples + default: False + +source-repository head + type: darcs + location: http://www.ariis.it/link/repos/ansi-terminal-game/ + +library + exposed-modules: Terminal.Game + other-modules: Terminal.Game.Animation, + Terminal.Game.Character, + Terminal.Game.Draw, + Terminal.Game.Layer.Imperative, + Terminal.Game.Layer.Object, + Terminal.Game.Layer.Object.GameIO, + Terminal.Game.Layer.Object.Interface, + Terminal.Game.Layer.Object.IO, + Terminal.Game.Layer.Object.Narrate, + Terminal.Game.Layer.Object.Primitive, + Terminal.Game.Layer.Object.Record, + Terminal.Game.Layer.Object.Test, + Terminal.Game.Utils, + Terminal.Game.Plane, + Terminal.Game.Random, + Terminal.Game.Timer + build-depends: base == 4.*, + ansi-terminal == 0.11.*, + array == 0.5.*, + bytestring >= 0.10 && < 0.12, + cereal == 0.5.*, + clock >= 0.7 && < 0.9, + containers == 0.6.*, + exceptions == 0.10.*, + linebreak == 1.1.*, + mintty == 0.1.*, + mtl == 2.2.*, + QuickCheck >= 2.13 && < 2.15, + random >= 1.2 && < 1.3, + split == 0.2.*, + terminal-size == 0.3.*, + unidecode >= 0.1.0 && < 0.2, + timers-tick > 0.5 && < 0.6 + hs-source-dirs: src + default-language: Haskell2010 + ghc-options: -Wall + + if os(windows) + hs-source-dirs: platform-dep/windows + if !os(windows) + hs-source-dirs: platform-dep/non-win + +test-suite test + default-language: Haskell2010 + hs-Source-Dirs: test, src, example + main-is: Test.hs + other-modules: Alone, + Balls, + Terminal.Game, + Terminal.Game.Animation, + Terminal.Game.Character, + Terminal.Game.Draw, + Terminal.Game.DrawSpec, + Terminal.Game.Layer.Imperative, + Terminal.Game.Layer.ImperativeSpec, + Terminal.Game.Layer.Object, + Terminal.Game.Layer.Object.GameIO, + Terminal.Game.Layer.Object.Interface, + Terminal.Game.Layer.Object.IO, + Terminal.Game.Layer.Object.Narrate, + Terminal.Game.Layer.Object.Primitive, + Terminal.Game.Layer.Object.Record, + Terminal.Game.Layer.Object.Test, + Terminal.Game.Utils, + Terminal.Game.Plane, + Terminal.Game.PlaneSpec + Terminal.Game.Random, + Terminal.Game.RandomSpec + build-depends: base == 4.*, + ansi-terminal == 0.11.*, + array == 0.5.*, + bytestring >= 0.10 && < 0.12, + cereal == 0.5.*, + clock >= 0.7 && < 0.9, + containers == 0.6.*, + exceptions == 0.10.*, + linebreak == 1.1.*, + mintty == 0.1.*, + mtl == 2.2.*, + QuickCheck >= 2.13 && < 2.15, + random >= 1.2 && < 1.3, + split == 0.2.*, + terminal-size == 0.3.*, + unidecode >= 0.1.0 && < 0.2, + timers-tick > 0.5 && < 0.6 + -- the above plus hspec + , hspec + type: exitcode-stdio-1.0 + ghc-options: -Wall + + if os(windows) + hs-source-dirs: platform-dep/windows + if !os(windows) + hs-source-dirs: platform-dep/non-win + +executable alone + if flag(examples) + build-depends: base == 4.*, + ansi-terminal-game + else + buildable: False + + hs-source-dirs: example + main-is: MainAlone.hs + other-modules: Alone + default-language: Haskell2010 + ghc-options: -threaded + -Wall + +executable alone-playback + if flag(examples) + build-depends: base == 4.*, + ansi-terminal-game, + temporary == 1.3.* + else + buildable: False + + hs-source-dirs: example + main-is: MainPlayback.hs + other-modules: Alone + default-language: Haskell2010 + ghc-options: -threaded + -Wall + +executable balls + if flag(examples) + build-depends: base == 4.*, + ansi-terminal-game + else + buildable: False + + hs-source-dirs: example + main-is: MainBalls.hs + other-modules: Balls + default-language: Haskell2010 + ghc-options: -threaded + -Wall + +executable hot-reload + if flag(examples) + build-depends: base == 4.*, + ansi-terminal-game + else + buildable: False + + hs-source-dirs: example + main-is: MainHotReload.hs + other-modules: Alone + default-language: Haskell2010 + ghc-options: -threaded + -Wall diff --git a/vendored/ansi-terminal-game-1.8.0.0/example/Alone.hs b/vendored/ansi-terminal-game-1.8.0.0/example/Alone.hs new file mode 100644 index 0000000..eeb64e2 --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/example/Alone.hs @@ -0,0 +1,90 @@ +module Alone where + +-- Alone in a room, game definition (logic & draw) +-- run with: cabal new-run -f examples alone + +import Terminal.Game + +import qualified Data.Tuple as T + +-- game specification +aloneInARoom :: Game MyState +aloneInARoom = Game 13 -- ticks per second + (MyState (10, 10) + Stop False) -- init state + (\_ s e -> logicFun s e) -- logic function + (\r s -> centerFull r $ + drawFun s) -- draw function + gsQuit -- quit function + +sizeCheck :: IO () +sizeCheck = let (w, h) = T.swap . snd $ boundaries + in assertTermDims w h + +------------------------------------------------------------------------------- +-- Types + +data MyState = MyState { gsCoord :: Coords, + gsMove :: Move, + gsQuit :: Bool } + deriving (Show, Eq) + +data Move = N | S | E | W | Stop + deriving (Show, Eq) + +boundaries :: (Coords, Coords) +boundaries = ((1, 1), (24, 80)) + +------------------------------------------------------------------------------- +-- Logic + +logicFun :: MyState -> Event -> MyState +logicFun gs (KeyPress 'q') = gs { gsQuit = True } +logicFun gs Tick = gs { gsCoord = pos (gsMove gs) (gsCoord gs) } +logicFun gs (KeyPress c) = gs { gsMove = move (gsMove gs) c } + +-- SCI movement +move :: Move -> Char -> Move +move N 'w' = Stop +move S 's' = Stop +move W 'a' = Stop +move E 'd' = Stop +move _ 'w' = N +move _ 's' = S +move _ 'a' = W +move _ 'd' = E +move m _ = m + +pos :: Move -> (Width, Height) -> (Width, Height) +pos m oldcs | oob newcs = oldcs + | otherwise = newcs + where + newcs = new m oldcs + + new Stop cs = cs + new N (r, c) = (r-1, c ) + new S (r, c) = (r+1, c ) + new E (r, c) = (r , c+1) + new W (r, c) = (r , c-1) + + ((lr, lc), (hr, hc)) = boundaries + oob (r, c) = r <= lr || c <= lc || + r >= hr || c >= hc + +------------------------------------------------------------------------------- +-- Draw + +drawFun :: MyState -> Plane +drawFun (MyState (r, c) _ _) = + blankPlane mw mh & + (1, 1) % box mw mh '-' & + (2, 2) % box (mw-2) (mh-2) ' ' & + (15, 20) % textBox 10 4 + "Tap WASD to move, tap again to stop." & + (20, 60) % textBox 8 10 + "Press Q to quit." # color Blue Vivid & + (r, c) % cell '@' # invert + where + mh :: Height + mw :: Width + (mh, mw) = snd boundaries diff --git a/vendored/ansi-terminal-game-1.8.0.0/example/Balls.hs b/vendored/ansi-terminal-game-1.8.0.0/example/Balls.hs new file mode 100644 index 0000000..2e39584 --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/example/Balls.hs @@ -0,0 +1,189 @@ +module Balls where + +-- library module for `balls` + +import Terminal.Game + +import qualified Data.Bool as B +import qualified Data.Ix as I +import qualified Data.Maybe as M +import qualified Data.Tuple as T + +{- + There are three things I will showcase in this example: + + 1. ** How you can display current FPS. ** + This is done using `Game` to create your game rather than + `simpleGame`. `Game` is a bit more complex but you gain + additional infos to manipulate/blit, like FPS. + + 2. ** How your game can gracefully handle screen resize. ** + Notice how if you resize the terminal, balls will still + fill the entire screen. This is again possible using `Game` + and the information passed via GameEnv (in this case, terminal + dimensions). + + 3. ** That — while FPS can change — game speed does not. ** + Check the timer: even when screen is crowded and frames are + dropped, it is not slowed down. + + + This game runs at 60 FPS, you will almost surely never need such + a high TPS! 15–20 is more than enough in most cases. +-} + +------------------------------------------------------------------------------- +-- Ball + +data Ball = Ball { pChar :: Plane, + pSpeed :: Timed Bool, + pDir :: Coords, + pPos :: Coords } + +-- change direction is necessary, then and move +modPar :: Dimensions -> Ball -> Maybe Ball +modPar ds b@(Ball _ _ d _) = + -- tick the ball and check it is time to move + let b' = tickBall b in + if not (fetchFrame . pSpeed $ b') + then Just b' -- no time to move for you + else + + -- check all popssible directions + let pd = [d, togR d, togC d, togB d] + bs = map (\ld -> b' { pDir = ld }) pd + bs' = filter (isIn ds) $ map modPos bs in + + -- returns a moved ball nor nothing to mark it “to eliminate” + case bs' of + [] -> Nothing + (cp:_) -> Just cp + where + togR (wr, wc) = (-wr, wc) + togC (wr, wc) = ( wr, -wc) + togB (wr, wc) = (-wr, -wc) + +tickBall :: Ball -> Ball +tickBall b = b { pSpeed = tick (pSpeed b) } + +modPos :: Ball -> Ball +modPos (Ball p t d@(dr, dc) (r, c)) = Ball p t d (r+dr, c+dc) + +isIn :: Dimensions -> Ball -> Bool +isIn (w, h) (Ball p _ _ (pr, pc)) = + let (pw, ph) = planeSize p + in pr >= 1 && + pr+ph-1 <= h && + pc >= 1 && + pc+pw-1 <= w + +dpart :: Ball -> (Coords, Plane) +dpart (Ball p _ _ cs) = (cs, p) + +genBall :: StdGen -> Dimensions -> (Ball, StdGen) +genBall g ds = + let (c, g1) = pickRandom [minBound..] g + (s, g2) = getRandom (1, 3) g1 + (v, g3) = pickRandom dirs g2 + (p, g4) = ranIx ((1,1), T.swap ds) g3 + b = Ball (cell 'o' # color c Vivid) + (creaBoolTimerLoop s) v p + in (b, g4) + where + dirs = [(1, 1), (1, -1), (-1, 1), (-1, -1)] + + -- tuples instances are yet to be added to `random` + -- as nov 21; this will do meanwhile. + ranIx :: I.Ix a => (a, a) -> StdGen -> (a, StdGen) + ranIx r wg = pickRandom (I.range r) wg + +------------------------------------------------------------------------------- +-- Timer + +type Timer = (Timed Bool, Integer) + +ctimer :: TPS -> Timer +ctimer tps = (creaBoolTimerLoop tps, 0) + +ltimer :: Timer -> Timer +ltimer (t, i) = let t' = tick t + k = B.bool 0 1 (fetchFrame t') + in (t', i+k) + +dtimer :: Timer -> Plane +dtimer (_, i) = word . show $ i + +------------------------------------------------------------------------------- +-- Game + +data GState = GState { gen :: StdGen, + quit :: Bool, + timer :: Timer, + balls :: [Ball], + bslow :: Bool } + -- pSlow is not used in game, it is there just + -- for the test suite + +fireworks :: StdGen -> Game GState +fireworks g = Game tps istate lfun dfun qfun + where + tps = 60 + + istate :: GState + istate = GState g False (ctimer tps) [] False + +------------------------------------------------------------------------------- +-- Logic + +lfun :: GEnv -> GState -> Event -> GState +lfun e s (KeyPress 's') = + let g = gen s + ds = eTermDims e + (b, g1) = genBall g ds + in s { gen = g1, + balls = b : balls s } +lfun _ s (KeyPress 'q') = s { quit = True } +lfun _ s (KeyPress _) = s +lfun r s Tick = + let ds = eTermDims r + + ps = balls s + ps' = M.mapMaybe (modPar ds) ps + + bs = eFPS r < 30 + in s { timer = ltimer (timer s), + balls = filter (isIn ds) ps', + bslow = bs } + +qfun :: GState -> Bool +qfun s = quit s + +------------------------------------------------------------------------------- +-- Draw + +dfun :: GEnv -> GState -> Plane +dfun r s = mergePlanes + (uncurry blankPlane ds) + (map dpart $ balls s) & + (1, 2) %^> tui # trans & + (1, 2) %.< inst # trans # bold + where + ds = eTermDims r + tm = timer s + + tui :: Plane + tui = let fps = eFPS r + np = length $ balls s + + l1 = word "FPS: " ||| word (show fps) + l2 = word "Timer: " ||| dtimer tm + l3 = word ("Balls: " ++ show np) + l4 = word ("Term. dims.: " ++ show ds) + in vcat [l1, l2, l3, l4] + + inst :: Plane + inst = word "Press (s) to spawn" === + word "Press (q) to quit" + + trans :: Draw + trans = makeTransparent ' ' diff --git a/vendored/ansi-terminal-game-1.8.0.0/example/MainAlone.hs b/vendored/ansi-terminal-game-1.8.0.0/example/MainAlone.hs new file mode 100644 index 0000000..c7149b9 --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/example/MainAlone.hs @@ -0,0 +1,12 @@ +module Main where + + +import Alone ( aloneInARoom, sizeCheck ) + +import Terminal.Game + +-- run with: cabal new-run -f examples alone + +main :: IO () +main = do sizeCheck + errorPress $ playGame aloneInARoom diff --git a/vendored/ansi-terminal-game-1.8.0.0/example/MainBalls.hs b/vendored/ansi-terminal-game-1.8.0.0/example/MainBalls.hs new file mode 100644 index 0000000..52655bc --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/example/MainBalls.hs @@ -0,0 +1,12 @@ +module Main where + +import Balls + +import Terminal.Game + +-- Balls Main module. The meat of the game is in `examples/Balls.hs` + +main :: IO () +main = getStdGen >>= \g -> + playGame (fireworks g) + diff --git a/vendored/ansi-terminal-game-1.8.0.0/example/MainHotReload.hs b/vendored/ansi-terminal-game-1.8.0.0/example/MainHotReload.hs new file mode 100644 index 0000000..3a0fb3d --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/example/MainHotReload.hs @@ -0,0 +1,31 @@ +module Main where + +import Alone ( aloneInARoom, sizeCheck ) + +import Terminal.Game + +-- Hot reloading is a handy feature while writing a game. Here I will +-- show you how to do that with ansi-terminal-game. +-- +-- 1. install `entr` from your repositories; +-- 2. run `find example/*.hs | entr -cr cabal run -f examples hot-reload`; +-- 3. now modify example/Alone.hs and see your changes live! +-- +-- Caveat: entr and similar applications do *not* work with interactive +-- programs, so you need — as shown below — to load a record and play +-- it as a demo. +-- This is still useful to iteratively build NPCs’ behaviour, GUIs, etc. +-- +-- Remember that you can use `recordGame` to record a session. If you +-- need something fancier for your game (e.g. hot-reload with input), +-- `venzone` [1] (module Watcher) has a builtin /watch mode/ you can +-- take inspiration from. +-- +-- [1] https://hackage.haskell.org/package/venzone + +main :: IO () +main = do + sizeCheck + gr <- readRecord "test/records/alone-record-test.gr" + -- check `readRecord + () <$ narrateGame aloneInARoom gr diff --git a/vendored/ansi-terminal-game-1.8.0.0/example/MainPlayback.hs b/vendored/ansi-terminal-game-1.8.0.0/example/MainPlayback.hs new file mode 100644 index 0000000..498275d --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/example/MainPlayback.hs @@ -0,0 +1,30 @@ +module Main where + +import Alone ( aloneInARoom, sizeCheck ) + +import Terminal.Game + +import System.IO.Temp ( emptySystemTempFile ) + +-- plays the game and, once you quit, shows a replay of the session +-- run with: cabal new-run -f examples alone-playback + +main :: IO () +main = do + sizeCheck + tf <- emptySystemTempFile "alone-record.gr" + playback tf + +playback :: FilePath -> IO () +playback f = do + prompt "Press to play the game." + recordGame aloneInARoom f + prompt "Press to watch playback." + es <- readRecord f + _ <- narrateGame aloneInARoom es + prompt "Playback over! Press to quit." + where + prompt :: String -> IO () + prompt s = putStrLn s >> () <$ getLine + + diff --git a/vendored/ansi-terminal-game-1.8.0.0/platform-dep/non-win/Terminal/Game/Utils.hs b/vendored/ansi-terminal-game-1.8.0.0/platform-dep/non-win/Terminal/Game/Utils.hs new file mode 100644 index 0000000..2276050 --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/platform-dep/non-win/Terminal/Game/Utils.hs @@ -0,0 +1,14 @@ +-------------------------------------------------------------------------------- +-- Nonbuffering getChar +-- 2017 Francesco Ariis GPLv3 80cols +-------------------------------------------------------------------------------- + +module Terminal.Game.Utils ( inputCharTerminal, + isWin32Console ) + where + +inputCharTerminal :: IO Char +inputCharTerminal = getChar + +isWin32Console :: IO Bool +isWin32Console = return False diff --git a/vendored/ansi-terminal-game-1.8.0.0/platform-dep/windows/Terminal/Game/Utils.hs b/vendored/ansi-terminal-game-1.8.0.0/platform-dep/windows/Terminal/Game/Utils.hs new file mode 100644 index 0000000..15f01b4 --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/platform-dep/windows/Terminal/Game/Utils.hs @@ -0,0 +1,29 @@ +-------------------------------------------------------------------------------- +-- Nonbuffering getChar et al +-- 2017 Francesco Ariis GPLv3 80cols +-------------------------------------------------------------------------------- +{-# LANGUAGE ForeignFunctionInterface #-} + +-- horrible horrible horrible hack to make unbuffered input +-- work on Windows (and win32console check) + +module Terminal.Game.Utils (inputCharTerminal, + isWin32Console ) + where + +import qualified Data.Char as C +import qualified Foreign.C.Types as FT +import qualified System.Console.MinTTY as M + +inputCharTerminal :: IO Char +inputCharTerminal = getCharWindows + +-- no idea why, but unsafe breaks it +getCharWindows :: IO Char +getCharWindows = fmap (C.chr . fromEnum) c_getch +foreign import ccall safe "conio.h getch" + c_getch :: IO FT.CInt + +-- not perfect, but it is what it is (on win, non minTTY) +isWin32Console :: IO Bool +isWin32Console = not <$> M.isMinTTY diff --git a/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game.hs b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game.hs new file mode 100644 index 0000000..c1bc144 --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game.hs @@ -0,0 +1,197 @@ +------------------------------------------------------------------------------- +-- | +-- Module : Terminal.Game +-- Copyright : © 2017-2021 Francesco Ariis +-- License : GPLv3 (see COPYING file) +-- +-- Maintainer : Francesco Ariis +-- Stability : provisional +-- Portability : portable +-- +-- Machinery and utilities for 2D terminal games. +-- +-- New? Start from 'Game'. +-- +------------------------------------------------------------------------------- + +-- Basic col-on-black ASCII terminal, operations. +-- Only module to be imported. + +module Terminal.Game ( -- * Running + TPS, + FPS, + Event(..), + GEnv(..), + Game(..), + playGame, + ATGException(..), + + -- ** Helpers + playGameS, + Terminal.Game.displaySize, + assertTermDims, + errorPress, + blankPlaneFull, + centerFull, + + -- * Game logic + -- | Some convenient function dealing with + -- Timers ('Timed') and 'Animation's. + -- + -- Usage of these is not mandatory: 'Game' is + -- parametrised over any state @s@, you are free + -- to implement game logic as you prefer. + + -- ** Timers/Animation + + -- *** Timers + Timed, + creaTimer, creaBoolTimer, + creaTimerLoop, creaBoolTimerLoop, + + -- *** Animations + Animation, + creaAnimation, + creaLoopAnimation, + + -- *** T/A interface + tick, ticks, reset, lapse, + fetchFrame, isExpired, + + -- ** Random numbers + StdGen, + getStdGen, mkStdGen, + getRandom, pickRandom, + UniformRange, + + -- * Drawing + -- | To get to the gist of drawing, check the + -- documentation for '%'. + -- + -- Blitting on screen is double-buffered and diff'd + -- (at each frame, only cells with changed character + -- will be redrawn). + + -- ** Plane + Plane, + Dimensions, + Coords, + Row, Column, + Width, Height, + blankPlane, + stringPlane, + stringPlaneTrans, + makeTransparent, + makeOpaque, + planePaper, + planeSize, + + -- ** Draw + Draw, + (%), (&), (#), + subPlane, + mergePlanes, + cell, word, box, + Color(..), ColorIntensity(..), + color, bold, invert, + + -- *** Alternative origins + -- $origins + (%^>), (%.<), (%.>), + + -- *** Text boxes + textBox, textBoxLiquid, + textBoxHyphen, textBoxHyphenLiquid, + Hyphenator, + -- | Eurocentric convenience reexports. Check + -- "Text.Hyphenation.Language" for more languages. + english_GB, english_US, esperanto, + french, german_1996, italian, spanish, + + -- *** Declarative drawing + (|||), (===), (***), hcat, vcat, + + -- * Testing + GRec, + recordGame, + readRecord, + testGame, + setupGame, + narrateGame, + + -- | A quick and dirty way to have /hot reload/ + -- (autorestarting your game when source files change) + -- is illustrated in @example/MainHotReload.hs@. + + -- * Cross platform + -- $xcompat + ) + where + +import System.Console.ANSI +import Terminal.Game.Animation +import Terminal.Game.Draw +import Terminal.Game.Layer.Imperative +import Terminal.Game.Layer.Object as O +import Terminal.Game.Plane +import Terminal.Game.Random +import Text.LineBreak + +import qualified Control.Monad as CM + +-- $origins +-- Placing a plane is sometimes more convenient if the coordinates origin +-- is a corner other than top-left (e.g. “Paste this plane one row from +-- bottom-left corner”). These combinators — meant to be used instead of '%' +-- — allow you to do so. Example: +-- +-- @ +-- prova :: Plane +-- prova = let rect = box 6 3 \'.\' +-- letters = word "ab" +-- in rect & +-- (1, 1) %.> letters -- start from bottom-right +-- +-- -- λ> putStr (planePaper prova) +-- -- ...... +-- -- ...... +-- -- ....ab +-- @ + +-- $xcompat +-- Good practices for cross-compatibility: +-- +-- * choose game dimensions of no more than __24 rows__ and __80 columns__. +-- This ensures compatibility with the trickiest terminals (i.e. Win32 +-- console); +-- +-- * use __ASCII characters__ only. Again this is for Win32 console +-- compatibility, until +-- [this GHC bug](https://gitlab.haskell.org/ghc/ghc/issues/7593) gets +-- fixed; +-- +-- * employ colour sparingly: as some users will play your game in a +-- light-background terminal and some in a dark one, choose only colours +-- that go well with either (blue, red, etc.); +-- +-- * some terminals/multiplexers (i.e. tmux) do not make a distinction +-- between vivid/dull; do not base your game mechanics on that +-- difference. + +-- | /Usable/ terminal display size (on Win32 console the last line is +-- set aside for input). Throws 'CannotGetDisplaySize' on error. +displaySize :: IO Dimensions +displaySize = O.displaySizeErr + +-- | Check if terminal can accomodate 'Dimensions', otherwise throws +-- 'DisplayTooSmall' with a helpful message for the player. +assertTermDims :: Width -> Height -> IO () +assertTermDims dw dh = + clearScreen >> + setCursorPosition 0 0 >> + displaySizeErr >>= \ads -> + CM.when (isSmaller ads) + (throwExc $ DisplayTooSmall (dw, dh) ads) + where + isSmaller :: Dimensions -> Bool + isSmaller (ww, wh) = ww < dw || wh < dh diff --git a/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Animation.hs b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Animation.hs new file mode 100644 index 0000000..64fe72f --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Animation.hs @@ -0,0 +1,33 @@ +------------------------------------------------------------------------------- +-- Animation +-- 2018 Francesco Ariis GPLv3 +------------------------------------------------------------------------------- + +-- {-# LANGUAGE DeriveGeneric #-} +-- {-# LANGUAGE DefaultSignatures #-} +-- {-# LANGUAGE StandaloneDeriving #-} +-- {-# LANGUAGE FlexibleInstances #-} + +module Terminal.Game.Animation (module Terminal.Game.Animation, + module T + ) where + +import Terminal.Game.Plane + +import Control.Timer.Tick as T + +-- import Data.Serialize + +-- import qualified Data.ByteString as BS +-- import qualified Data.Bifunctor as BF + +-- | An @Animation@ is a series of timed time-separated 'Plane's. +type Animation = T.Timed Plane + +-- | Creates an 'Animation'. +creaAnimation :: [(Integer, Plane)] -> Animation +creaAnimation ips = creaTimedRes (Times 1 Elapse) ips + +-- | Creates a looped 'Animation'. +creaLoopAnimation :: [(Integer, Plane)] -> Animation +creaLoopAnimation ips = creaTimedRes AlwaysLoop ips diff --git a/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Character.hs b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Character.hs new file mode 100644 index 0000000..6cc91ca --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Character.hs @@ -0,0 +1,43 @@ +module Terminal.Game.Character where + +import Data.Char as C +import Text.Unidecode as D +import System.IO.Unsafe as U + + +import Terminal.Game.Utils + +-- Non ASCII character still cause crashes on Win32 console (see this +-- report: https://gitlab.haskell.org/ghc/ghc/issues/7593 ). +-- We provide a function to substitute them when playing on Win32 +-- console, with another appropriate chatacter. + +win32SafeChar :: Char -> Char +win32SafeChar c | areWeWin32 = toASCII c + | otherwise = c + where + areWeWin32 :: Bool + areWeWin32 = unsafePerformIO isWin32Console + +-- ANCILLARIES -- + +toASCII :: Char -> Char +toASCII c | C.isAscii c = c + | Just cm <- lu = cm -- hand-made substitution + | [cu] <- unidecode c = cu -- unidecode + | otherwise = '?' -- all else failing + where + lu = lookup c subDictionary + +subDictionary :: [(Char, Char)] +subDictionary = [ -- various open/close quotes + ('«', '<'), + ('»', '>'), + ('“', '\''), + ('”', '\''), + ('‘', '\''), + ('’', '\''), + + -- typographical marks + ('—', '-') ] + diff --git a/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Draw.hs b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Draw.hs new file mode 100644 index 0000000..e0255d0 --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Draw.hs @@ -0,0 +1,255 @@ +------------------------------------------------------------------------------- +-- Print convenience functions +-- 2017 Francesco Ariis GPLv3 +------------------------------------------------------------------------------- + +-- Drawing primitives. If not stated otherwise (textbox, etc.), ' ' are +-- assumed to be opaque + +module Terminal.Game.Draw (module Terminal.Game.Draw, + (F.&) + ) where + +import Terminal.Game.Plane + +import Text.LineBreak + +import qualified Data.Function as F ( (&) ) +import qualified Data.List as L +import qualified System.Console.ANSI as CA + + +----------- +-- TYPES -- +----------- + +-- | A drawing function, usually executed with the help of '%'. +type Draw = Plane -> Plane + + +----------------- +-- COMBINATORS -- +----------------- + +-- | Pastes one 'Plane' onto another. To be used along with 'F.&' +-- like this: +-- +-- @ +-- d :: Plane +-- d = blankPlane 100 100 & +-- (3, 4) % box '_' 3 5 & +-- (a, b) % cell \'A\' '#' bold +-- @ +(%) :: Coords -> Plane -> Draw +cds % p1 = \p2 -> pastePlane p1 p2 cds +infixl 4 % + +-- | Apply style to plane, e.g. +-- +-- > cell 'w' # bold +(#) :: Plane -> Draw -> Plane +p # sf = sf p +infixl 8 # + +-- | Shorthand for sequencing 'Plane's, e.g. +-- +-- @ +-- firstPlane & +-- (3, 4) '%' secondPlane & +-- (1, 9) '%' thirdPlane +-- @ +-- +-- is equal to +-- +-- @ +-- mergePlanes firstPlane [((3,4), secondPlane), +-- ((1,9), thirdPlane)] +-- @ +mergePlanes :: Plane -> [(Coords, Plane)] -> Plane +mergePlanes p cps = L.foldl' addPlane p cps + where + addPlane :: Plane -> (Coords, Plane) -> Plane + addPlane bp (cs, tp) = bp F.& cs % tp + +-- | Place two 'Plane's side-by-side, horizontally. +(|||) :: Plane -> Plane -> Plane +(|||) a b = let (wa, ha) = planeSize a + (wb, hb) = planeSize b + in mergePlanes (blankPlane (wa + wb) (max ha hb)) + [((1,1), a), + ((1,wa+1), b)] + +-- | Place two 'Plane's side-by-side, vertically. +(===) :: Plane -> Plane -> Plane +(===) a b = let (wa, ha) = planeSize a + (wb, hb) = planeSize b + in mergePlanes (blankPlane (max wa wb) (ha + hb)) + [((1,1), a), + ((ha+1,1), b)] + +-- | @a *** b@ blits @b@ in the centre of @a@. +(***) :: Plane -> Plane -> Plane +(***) a b = let (aw, ah) = planeSize a + (bw, bh) = planeSize b + r = quot (ah - bh) 2 + 1 + c = quot (aw - bw) 2 + 1 + in a F.& + (r, c) % b + + +-- | Place a list of 'Plane's side-by-side, horizontally. @error@s on +-- empty list. +hcat :: [Plane] -> Plane +hcat [] = blankPlane 1 1 # makeTransparent ' ' +hcat ps = L.foldl1' (|||) ps + +-- | Place a list of 'Plane's side-by-side, vertically. @error@s on +-- empty list. +vcat :: [Plane] -> Plane +vcat [] = blankPlane 1 1 # makeTransparent ' ' +vcat ps = L.foldl1' (===) ps + +infixl 6 |||, ===, *** + + +------------ +-- STYLES -- +------------ + +-- | Set foreground color. +color :: CA.Color -> CA.ColorIntensity -> Plane -> Plane +color c i p = mapPlane (colorCell c i) p + +-- | Apply bold style to 'Plane'. +bold :: Plane -> Plane +bold p = mapPlane boldCell p + +-- | Swap foreground and background colours of 'Plane'. +invert :: Plane -> Plane +invert p = mapPlane reverseCell p + + + +------------- +-- DRAWING -- +------------- + +-- | A box of dimensions @w h@. +box :: Width -> Height -> Char -> Plane +box w h chr = seqCellsDim w h cells + where + cells = [((r, c), chr) | r <- [1..h], c <- [1..w]] + +-- | A 1×1 @Plane@. +cell :: Char -> Plane +cell ch = box 1 1 ch + +-- | @1xn@ 'Plane' with a word in it. If you need to import multiline +-- ASCII art, check 'stringPlane' and 'stringPlaneTrans'. +word :: String -> Plane +word w = seqCellsDim (L.genericLength w) 1 cells + where + cells = zip (zip (repeat 1) [1..]) w + +-- opaque :: Plane -> Plane +-- opaque p = pastePlane p (box ' ' White w h) (1, 1) +-- where +-- (w, h) = pSize p + +-- | A text-box. Assumes @' '@s are transparent. +textBox :: Width -> Height -> String -> Plane +textBox w h cs = frameTrans w h (textBoxLiquid w cs) + +-- | Like 'textBox', but tall enough to fit @String@. +textBoxLiquid :: Width -> String -> Plane +textBoxLiquid w cs = textBoxGeneralLiquid Nothing w cs + +-- | As 'textBox', but hypenated. Example: +-- +-- @ +-- (normal textbox) (hyphenated textbox) +-- Rimasi un po’ a meditare nel buio Rimasi un po’ a meditare nel buio +-- velato appena dal barlume azzurrino velato appena dal barlume azzurrino +-- del fornello a gas, su cui del fornello a gas, su cui sobbol- +-- sobbolliva quieta la pentola. liva quieta la pentola. +-- @ +-- +-- Notice how in the right box /sobbolliva/ is broken in two. This +-- can be useful and aesthetically pleasing when textboxes are narrow. +textBoxHyphen :: Hyphenator -> Width -> Height -> String -> Plane +textBoxHyphen hp w h cs = frameTrans w h (textBoxHyphenLiquid hp w cs) + +-- | As 'textBoxLiquid', but hypenated. +textBoxHyphenLiquid :: Hyphenator -> Width -> String -> Plane +textBoxHyphenLiquid h w cs = textBoxGeneralLiquid (Just h) w cs + +textBoxGeneralLiquid :: Maybe Hyphenator -> Width -> String -> Plane +textBoxGeneralLiquid mh w cs = transparent + where + -- hypenathion + bf = BreakFormat (fromIntegral w) 4 '-' mh + hcs = breakStringLn bf cs + h = L.genericLength hcs + + f :: [String] -> [(Coords, Char)] + f css = concatMap (uncurry rf) (zip [1..] css) + where rf :: Int -> String -> [(Coords, Char)] + rf cr ln = zip (zip (repeat cr) [1..]) ln + + out = seqCellsDim w h (f hcs) + transparent = makeTransparent ' ' out + + +---------------------------- +-- ADDITIONAL COMBINATORS -- +---------------------------- + +-- Coords as if origin were @ bottom-right +recipCoords :: Coords -> Plane -> Plane -> Coords +recipCoords (r, c) p p1 = + let (pw, ph) = planeSize p + (p1w, p1h) = planeSize p1 + r' = ph-p1h-r+2 + c' = pw-p1w-c+2 + in (r', c') + +-- | Pastes a plane onto another (origin: top-right). +(%^>) :: Coords -> Plane -> Draw +(r, c) %^> p1 = \p -> + let (_, c') = recipCoords (r, c) p p1 + in p F.& (r, c') % p1 + +-- | Pastes a plane onto another (origin: bottom-left). +(%.<) :: Coords -> Plane -> Draw +(r, c) %.< p1 = \p -> + let (r', _) = recipCoords (r, c) p p1 + in p F.& (r', c) % p1 + +-- | Pastes a plane onto another (origin: bottom-right). +(%.>) :: Coords -> Plane -> Draw +cs %.> p1 = \p -> + let (r', c') = recipCoords cs p p1 + in p F.& (r', c') % p1 + +infixl 4 %^> +infixl 4 %.< +infixl 4 %.> + + +----------------- +-- ANCILLARIES -- +----------------- + +seqCellsDim :: Width -> Height -> [(Coords, Char)] -> Plane +seqCellsDim w h cells = seqCells (blankPlane w h) cells + +seqCells :: Plane -> [(Coords, Char)] -> Plane +seqCells p cells = updatePlane p (map f cells) + where + f (cds, chr) = (cds, creaCell chr) + +-- paste plane on a blank one, and make ' ' transparent +frameTrans :: Width -> Height -> Plane -> Plane +frameTrans w h p = let bt = makeTransparent ' ' (blankPlane w h) + in bt F.& (1, 1) % p + diff --git a/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Imperative.hs b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Imperative.hs new file mode 100644 index 0000000..6c6c8bb --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Imperative.hs @@ -0,0 +1,258 @@ +------------------------------------------------------------------------------- +-- Layer 1 (imperative), as per +-- https://www.parsonsmatt.org/2018/03/22/three_layer_haskell_cake.html +-- 2019 Francesco Ariis GPLv3 +------------------------------------------------------------------------------- + +{-# Language ScopedTypeVariables #-} + +module Terminal.Game.Layer.Imperative where + +import Terminal.Game.Draw +import Terminal.Game.Layer.Object + +import qualified Control.Concurrent as CC +import qualified Control.Exception as E +import qualified Control.Monad as CM +import qualified Data.Bool as B +import qualified Data.List as D +import qualified System.IO as SI + +import Terminal.Game.Plane + +-- | Game definition datatype, parametrised on your gamestate. The two most +-- important elements are the function dealing with logic and the drawing +-- one. Check @alone@ demo (@cabal run -f examples alone@) to see a simple +-- game in action. +data Game s = + Game { gTPS :: TPS, + -- ^ Game speed in ticks per second. You do not + -- need high values, since the 2D canvas is coarse + -- (e.g. 13 TPS is enough for action games). + gInitState :: s, -- ^ Initial state of the game. + gLogicFunction :: GEnv -> s -> Event -> s, + -- ^ Logic function. + gDrawFunction :: GEnv -> s -> Plane, + -- ^ Draw function. Just want to blit your game + -- in the middle? Check 'centerFull'. + gQuitFunction :: s -> Bool + -- ^ /Should I quit?/ function. + } + +-- | A blank plane as big as the terminal. +blankPlaneFull :: GEnv -> Plane +blankPlaneFull e = uncurry blankPlane (eTermDims e) + +-- | Blits plane in the middle of terminal. +-- +-- @ +-- draw :: GEnv -> MyState -> Plane +-- draw ev s = +-- centerFull ev $ +-- ⁝ +-- @ +centerFull :: GEnv -> Plane -> Plane +centerFull e p = blankPlaneFull e *** p + +-- | Entry point for the game execution, should be called in @main@. +-- +-- You __must__ compile your programs with @-threaded@; if you do not do +-- this the game will crash at start-up. Just add: +-- +-- @ +-- ghc-options: -threaded +-- @ +-- +-- in your @.cabal@ file and you will be fine! +-- +-- Need to inspect state on exit? Check 'playGameS'. +playGame :: Game s -> IO () +playGame g = () <$ runGIO (runGameGeneral g) + +-- | As 'playGame', but do not discard state. +playGameS :: Game s -> IO s +playGameS g = runGIO (runGameGeneral g) + +-- | Tests a game in a /pure/ environment. Aims to accurately emulate 'GEnv' +-- changes (screen size, FPS) too. +testGame :: Game s -> GRec -> s +testGame g ts = fst $ runTest (runGameGeneral g) ts + +-- | As 'testGame', but returns 'Game' instead of a bare state. +-- Useful to fast-forward (e.g.: skip menus) before invoking 'playGame'. +setupGame :: Game s -> GRec -> Game s +setupGame g ts = let s' = testGame g ts + in g { gInitState = s' } + -- xx qua messi solo [Event]? + +-- | Similar to 'testGame', runs the game given a 'GRec'. Unlike +-- 'testGame', the playthrough will be displayed on screen. Useful when a +-- test fails and you want to see how. +-- +-- See this in action with @cabal run -f examples alone-playback@. +-- +-- Notice that 'GEnv' will be provided at /run-time/, and not +-- record-time; this can make emulation slightly inaccurate if — e.g. — +-- you replay the game on a smaller terminal than the one you recorded +-- the session on. +narrateGame :: Game s -> GRec -> IO s +narrateGame g e = runReplay (runGameGeneral g) e + +-- | Play as in 'playGame' and write the session to @file@. Useful to +-- produce input for 'testGame' and 'narrateGame'. Session will be +-- recorded even if an exception happens while playing. +recordGame :: Game s -> FilePath -> IO () +recordGame g fp = + E.bracket + (CC.newMVar igrec) + (\ve -> writeRec fp ve) + (\ve -> () <$ runRecord (runGameGeneral g) ve) + +data Config = Config { cMEvents :: CC.MVar [Event], + cTPS :: TPS } + +runGameGeneral :: forall s m. MonadGameIO m => + Game s -> m s +runGameGeneral (Game tps s lf df qf) = + -- init + setupDisplay >> + startEvents tps >>= \(InputHandle ve ts) -> + displaySizeErr >>= \ds -> + + -- do it! + let c = Config ve tps in + cleanUpErr (game c ds) + -- this under will be run regardless + (stopEvents ts >> + shutdownDisplay ) + where + game :: MonadGameIO m => Config -> Dimensions -> m s + game c wds = gameLoop c s lf df qf + Nothing wds + (creaFPSCalc tps) + +-- | Wraps an @IO@ computation so that any 'ATGException' or 'error' gets +-- displayed along with a @\@ prompt. +-- Some terminals shut-down immediately upon program end; adding +-- @errorPress@ to 'playGame' makes it easier to beta-test games on those +-- terminals. +errorPress :: IO a -> IO a +errorPress m = E.catches m [E.Handler errorDisplay, + E.Handler atgDisplay] + where + errorDisplay :: E.ErrorCall -> IO a + errorDisplay (E.ErrorCallWithLocation cs l) = report $ + putStrLn (cs ++ "\n\n") >> + putStrLn "Stack trace info:\n" >> + putStrLn l + + atgDisplay :: ATGException -> IO a + atgDisplay e = report $ print e + + report :: IO () -> IO a + report wm = + putStrLn "ERROR REPORT\n" >> + wm >> + putStrLn "\n\n " >> + SI.hSetBuffering SI.stdin SI.NoBuffering >> + getChar >> + errorWithoutStackTrace "errorPress" + + +----------- +-- LOGIC -- +----------- + +-- from http://www.loomsoft.net/resources/alltut/alltut_lesson6.htm +gameLoop :: MonadGameIO m => + Config -> -- event source + s -> -- state + (GEnv -> + s -> Event -> s) -> -- logic function + (GEnv -> + s -> Plane) -> -- draw function + (s -> Bool) -> -- quit? function + Maybe Plane -> -- last blitted screen + Dimensions -> -- Term dimensions + FPSCalc -> -- calculate fps + m s +gameLoop c s lf df qf opln td fps = + + -- quit? + checkQuit qf s >>= \qb -> + if qb + then return s + else + + -- fetch events (if any) + pollEvents (cMEvents c) >>= \es -> + + -- no events? skip everything + if null es + then sleepABit (cTPS c) >> + gameLoop c s lf df qf opln td fps + else + + displaySizeErr >>= \td' -> + + -- logic + let ge = GEnv td' (calcFPS fps) + (i, s') = stepsLogic s (lf ge) es in + + -- no `Tick` events? You do not need to blit, just update state + if i == 0 + then gameLoop c s' lf df qf opln td fps + else + + -- FPS calc + let fps' = addFPS i fps in + + -- clear screen if resolution changed + let resc = td /= td' in + CM.when resc clearDisplay >> + + -- draw + let opln' | resc = Nothing -- res changed? restart double buffering + | otherwise = opln + npln = df ge s' in + + blitPlane opln' npln >> + + gameLoop c s' lf df qf (Just npln) td' fps' + +-- Int = number of `Tick` events +stepsLogic :: s -> (s -> Event -> s) -> [Event] -> (Integer, s) +stepsLogic s lf es = let ies = D.genericLength . filter isTick $ es + in (ies, foldl lf s es) + where + isTick Tick = True + isTick _ = False + +------------------------------------------------------------------------------- +-- Frame per Seconds + +data FPSCalc = FPSCalc [Integer] TPS + -- list with number of `Ticks` processed at each blit and expected + -- FPS (i.e. TPS) + +-- the size of moving average will be TPS (that simplifies calculations) +creaFPSCalc :: TPS -> FPSCalc +creaFPSCalc tps = FPSCalc (D.genericReplicate tps {- (tps*2) -} 1) tps + -- tps*1: size of thw window in **blit actions** (not tick actions!) + -- so keeping it small should be responsive and non flickery + -- at the same time! + +-- add ticks +addFPS :: Integer -> FPSCalc -> FPSCalc +addFPS nt (FPSCalc (_:fps) tps) = FPSCalc (fps ++ [nt]) tps +addFPS _ (FPSCalc [] _) = error "addFPS: empty list." + +calcFPS :: FPSCalc -> Integer +calcFPS (FPSCalc fps tps) = + let ts = sum fps + ds = D.genericLength fps + in roundQuot (tps * ds) ts + where + roundQuot :: Integer -> Integer -> Integer + roundQuot a b = let (q, r) = quotRem a b + in q + B.bool 0 1 (r > div b 2) diff --git a/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object.hs b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object.hs new file mode 100644 index 0000000..a7c0819 --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object.hs @@ -0,0 +1,30 @@ +------------------------------------------------------------------------------- +-- Layer 2 (mockable IO), as per +-- https://www.parsonsmatt.org/2018/03/22/three_layer_haskell_cake.html +-- 2019 Francesco Ariis GPLv3 +------------------------------------------------------------------------------- + +module Terminal.Game.Layer.Object ( module Export ) where + +import Terminal.Game.Layer.Object.Interface as Export +import Terminal.Game.Layer.Object.GameIO as Export +import Terminal.Game.Layer.Object.Narrate as Export +import Terminal.Game.Layer.Object.Primitive as Export +import Terminal.Game.Layer.Object.Record as Export +import Terminal.Game.Layer.Object.Test as Export + +-- DESIGN NOTES -- + +-- The classes are described in 'Interface'. + +-- The implemented monads are four: +-- - GameIO (via MonadIO): playing the game +-- - Test: testing the game in a pure manner +-- - Record: playing the game and record the Events in a file +-- - Replay: replay a game using a set of Events. +-- +-- The last two monads (Record/Replay) take advantage of "overlapping +-- instances". Instead of reimplementing most of what happens in MonadIO, +-- they'll just touch the classes from interface which behaviour they +-- will modify; being more specific, they will be chosen instead of plain +-- IO. diff --git a/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object/GameIO.hs b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object/GameIO.hs new file mode 100644 index 0000000..a6d8e14 --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object/GameIO.hs @@ -0,0 +1,21 @@ +------------------------------------------------------------------------------- +-- Layer 2 (mockable IO), as per +-- https://www.parsonsmatt.org/2018/03/22/three_layer_haskell_cake.html +-- 2019 Francesco Ariis GPLv3 +------------------------------------------------------------------------------- + +{-# LANGUAGE GeneralizedNewtypeDeriving #-} + + +module Terminal.Game.Layer.Object.GameIO where + +import qualified Control.Monad.Catch as MC +import qualified Control.Monad.Trans as T + + +newtype GameIO a = GameIO { runGIO :: IO a } + deriving (Functor, Applicative, Monad, + T.MonadIO, + MC.MonadThrow, MC.MonadCatch, MC.MonadMask) + + diff --git a/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object/IO.hs b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object/IO.hs new file mode 100644 index 0000000..bbe8aa1 --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object/IO.hs @@ -0,0 +1,290 @@ +------------------------------------------------------------------------------- +-- Layer 2 (mockable IO), as per +-- https://www.parsonsmatt.org/2018/03/22/three_layer_haskell_cake.html +-- 2019 Francesco Ariis GPLv3 +------------------------------------------------------------------------------- + +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE UndecidableInstances #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + + +module Terminal.Game.Layer.Object.IO where + +import Terminal.Game.Utils + +import Terminal.Game.Layer.Object.Interface +import Terminal.Game.Layer.Object.Primitive +import Terminal.Game.Plane + +import qualified Control.Concurrent as CC +import qualified Control.Monad as CM +import qualified Control.Monad.Catch as MC +import qualified Control.Monad.Trans as T +import qualified Data.List.Split as LS +import qualified System.Clock as SC +import qualified System.Console.ANSI as CA +import qualified System.Console.Terminal.Size as TS +import qualified System.IO as SI + +-- Most General MonadIO operations. + +---------------- +-- Game input -- +---------------- + +instance {-# OVERLAPS #-} (Monad m, T.MonadIO m) => MonadInput m where + startEvents tps = T.liftIO $ startIOInput tps + pollEvents ve = T.liftIO $ CC.swapMVar ve [] + stopEvents ts = T.liftIO $ stopEventsIO ts + +-- filepath = logging +startIOInput :: TPS -> IO InputHandle +startIOInput tps = + SI.hSetBuffering SI.stdin SI.NoBuffering >> + SI.hSetBuffering SI.stdout SI.NoBuffering >> + SI.hSetEcho SI.stdin False >> + -- all the buffering settings has to happen + -- at the top of startIOInput. If i move + -- them to display, you need to press enter + -- before playing the game on some machines. + + -- event and log variables + CC.newMVar [] >>= \ve -> + + getTimeTick tps >>= \it -> + CC.forkIO (addTick ve tps it) >>= \te -> + CC.forkIO (addKeypress ve) >>= \tk -> + return (InputHandle ve [te, tk]) + +-- a precise timer, not based on `threadDelay` +type Elapsed = Integer -- in `Ticks` + +-- elapsed from Epoch in ticks +getTimeTick :: TPS -> IO Elapsed +getTimeTick tps = + getTime >>= \tm -> + let ns = 10 ^ (9 :: Integer) + t1 = quot ns tps in + return (quot tm t1) + +-- mr: maybe recording +addTick :: CC.MVar [Event] -> TPS -> Elapsed -> IO () +addTick ve tps el = + -- precise timing. With `treadDelay`, on finer TPS, + -- ticks take too much (check threadDelay doc). + getTimeTick tps >>= \t -> + CM.replicateM_ (fromIntegral $ t-el) + (addEvent ve Tick) >> + + -- sleep some + sleepABit tps >> + addTick ve tps t + +-- get action char +-- mr: maybe recording +addKeypress :: CC.MVar [Event] -> IO () +addKeypress ve = -- vedi platform-dep/ + inputCharTerminal >>= \c -> + addEvent ve (KeyPress c) >> + addKeypress ve + +-- mr: maybe recording +addEvent :: CC.MVar [Event] -> Event -> IO () +addEvent ve e = vf ve + where + vf d = CC.modifyMVar_ d (return . (++[e])) + +stopEventsIO :: [CC.ThreadId] -> IO () +stopEventsIO ts = mapM_ CC.killThread ts + +----------------- +-- Game timing -- +----------------- + +instance {-# OVERLAPS #-} (Monad m, T.MonadIO m) => MonadTimer m where + getTime = T.liftIO $ SC.toNanoSecs <$> SC.getTime SC.Monotonic + sleepABit tps = T.liftIO $ + CC.threadDelay (fromIntegral $ quot oneTickSec (tps*10)) + +-------------------- +-- Error handling -- +-------------------- + +instance {-# OVERLAPS #-} + (Monad m, T.MonadIO m, MC.MonadMask m, MC.MonadThrow m) => + MonadException m where + cleanUpErr m c = MC.finally m c + throwExc t = MC.throwM t + +----------- +-- Logic -- +----------- + +instance {-# OVERLAPS #-} (Monad m, T.MonadIO m) => + MonadLogic m where + checkQuit fb s = return (fb s) + +------------- +-- Display -- +------------- + +instance {-# OVERLAPS #-} (Monad m, T.MonadIO m) => MonadDisplay m where + setupDisplay = T.liftIO initPart + clearDisplay = T.liftIO clearScreen + displaySize = T.liftIO displaySizeIO + blitPlane mp p = T.liftIO (blitPlaneIO mp p) + shutdownDisplay = T.liftIO cleanAndExit + +displaySizeIO :: IO (Maybe Dimensions) +displaySizeIO = + TS.size >>= \ts -> + -- cannot use ansi-terminal, on Windows you get + -- "ConsoleException 87" (too much scrolling) + -- and it does not work for mintty and it is + -- inefficient as it gets (attempts to scroll past + -- bottom right) + isWin32Console >>= \bw -> + + return (fmap (f bw) ts) + where + f :: Bool -> TS.Window Int -> Dimensions + f wbw (TS.Window h w) = + let h' | wbw = h - 1 + | otherwise = h + in (w, h') + +-- pn: new plane, po: old plane +-- wo, ho: dimensions of the terminal. If they change, reinit double buffering +blitPlaneIO :: Maybe Plane -> Plane -> IO () +blitPlaneIO mpo pn = + + -- remember that Nothing will be passed: + -- - at the beginning of the game (first blit) + -- - when resolution changes (see gameLoop) + -- so do not duplicate hasResChanged checks here! + + -- old plane + let + (pw, ph) = planeSize pn + bp = blankPlane pw ph + po = pastePlane (maybe bp id mpo) bp (1, 1) + in + + -- new plane + let pn' = pastePlane pn bp (1, 1) + in + + -- trimming is foundamental, as blitMap could otherwise print + -- outside terminal boundaries and scroll to its death + -- (error 87 on Win32 console). + + CA.setSGR [CA.Reset] >> + blitMap po pn' + + +----------------- +-- ANCILLARIES -- +----------------- + +initPart :: IO () +initPart = -- check thread support + CM.unless CC.rtsSupportsBoundThreads + (error errMes) >> + + -- initial setup/checks + CA.hideCursor >> + + -- text encoding + SI.mkTextEncoding "UTF-8//TRANSLIT" >>= \te -> + SI.hSetEncoding SI.stdout te >> + + clearScreen + where + errMes = unlines + ["\nError: you *must* compile this program with -threaded!", + "Just add", + "", + " ghc-options: -threaded", + "", + "in your .cabal file (executable section) and you will be fine!"] + +-- clears screen +clearScreen :: IO () +clearScreen = CA.setCursorPosition 0 0 >> + CA.setSGR [CA.Reset] >> + displaySizeErr >>= \(w, h) -> + CM.replicateM_ (fromIntegral $ w*h) (putChar ' ') + +cleanAndExit :: IO () +cleanAndExit = CA.setSGR [CA.Reset] >> + CA.clearScreen >> + CA.setCursorPosition 0 0 >> + CA.showCursor + +-- plane +blitMap :: Plane -> Plane -> IO () +blitMap po pn = + CM.when (planeSize po /= planeSize pn) + (error "blitMap: different plane sizes") >> + CA.setCursorPosition 0 0 >> + -- setCursorPosition is *zero* based! + blitToTerminal (0, 0) (orderedCells po) (orderedCells pn) + +orderedCells :: Plane -> [[Cell]] +orderedCells p = LS.chunksOf (fromIntegral w) cells + where + cells = map snd $ assocsPlane p + (w, _) = planeSize p + + +-- ordered sequence of cells, both old and new, like they were a String to +-- print to screen. +-- Coords: initial blitting position +-- Remember that this Column is *zero* based +blitToTerminal :: Coords -> [[Cell]] -> [[Cell]] -> IO () +blitToTerminal (rr, rc) ocs ncs = CM.foldM_ blitLine rr oldNew + where + oldNew :: [[(Cell, Cell)]] + oldNew = zipWith zip ocs ncs + + -- row = previous row + blitLine :: Row -> [(Cell, Cell)] -> IO Row + blitLine pr ccs = + CM.foldM_ blitCell 0 ccs >> + -- have to use setCursorPosition (instead of nextrow) b/c + -- on win there is an auto "go-to-next-line" when reaching + -- column end and on win it does not do so + let wr = pr + 1 in + CA.setCursorPosition (fromIntegral wr) + (fromIntegral rc) >> + return wr + + -- k is "spaces to skip" + blitCell :: Int -> (Cell, Cell) -> IO Int + blitCell k (clo, cln) + | cln == clo = return (k+1) + | otherwise = moveIf k >>= \k' -> + putCellStyle cln >> + return k' + + moveIf :: Int -> IO Int + moveIf k | k == 0 = return k + | otherwise = CA.cursorForward k >> + return 0 + +putCellStyle :: Cell -> IO () +putCellStyle c = CA.setSGR ([CA.Reset] ++ sgrb ++ sgrr ++ sgrc) >> + putChar (cellChar c) + where + sgrb | isBold c = [CA.SetConsoleIntensity CA.BoldIntensity] + | otherwise = [] + + sgrr | isReversed c = [CA.SetSwapForegroundBackground True] + | otherwise = [] + + sgrc | Just (k, i) <- cellColor c = [CA.SetColor CA.Foreground i k] + | otherwise = [] + +oneTickSec :: Integer +oneTickSec = 10 ^ (6 :: Integer) diff --git a/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object/Interface.hs b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object/Interface.hs new file mode 100644 index 0000000..0994066 --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object/Interface.hs @@ -0,0 +1,89 @@ +------------------------------------------------------------------------------- +-- Layer 2 (mockable IO), as per +-- https://www.parsonsmatt.org/2018/03/22/three_layer_haskell_cake.html +-- 2019 Francesco Ariis GPLv3 +------------------------------------------------------------------------------- + +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE LambdaCase #-} + +module Terminal.Game.Layer.Object.Interface where + +import Terminal.Game.Plane +import Terminal.Game.Layer.Object.Primitive + +import qualified Control.Concurrent as CC +import qualified Control.Monad.Catch as MC + +------------------------------------------------------------------------------- +-- mtl interface for game + +type MonadGameIO m = (MonadInput m, MonadTimer m, + MonadException m, MonadLogic m, + MonadDisplay m) + +data InputHandle = InputHandle + { ihKeyMVar :: CC.MVar [Event], + ihOpenThreads :: [CC.ThreadId] } + +class Monad m => MonadInput m where + startEvents :: TPS -> m InputHandle + pollEvents :: CC.MVar [Event] -> m [Event] + stopEvents :: [CC.ThreadId] -> m () + +class Monad m => MonadTimer m where + getTime :: m Integer -- to nanoseconds + sleepABit :: TPS -> m () -- Given TPS, sleep a fracion of a single + -- Tick. + +-- if a fails, do b (useful for cleaning up) +class Monad m => MonadException m where + cleanUpErr :: m a -> m b -> m a + throwExc :: ATGException -> m a + +class Monad m => MonadLogic m where + -- decide whether it's time to quit + checkQuit :: (s -> Bool) -> s -> m Bool + +class Monad m => MonadDisplay m where + setupDisplay :: m () + clearDisplay :: m () + displaySize :: m (Maybe Dimensions) + blitPlane :: Maybe Plane -> Plane -> m () + shutdownDisplay :: m () + +displaySizeErr :: (MonadDisplay m, MonadException m) => m Dimensions +displaySizeErr = displaySize >>= \case + Nothing -> throwExc CannotGetDisplaySize + Just d -> return d + +------------------------------------------------------------------------------- +-- Error handling + +-- | @ATGException@s are thrown synchronously for easier catching. +data ATGException = CannotGetDisplaySize + | DisplayTooSmall Dimensions Dimensions + -- ^ Required and actual dimensions. + +instance Show ATGException where + show CannotGetDisplaySize = "CannotGetDisplaySize" + show (DisplayTooSmall (sw, sh) tds) = + let colS ww = ww < sw + rowS wh = wh < sh + + smallMsg :: Dimensions -> String + smallMsg (ww, wh) = + let cm = show ww ++ " columns" + rm = show wh ++ " rows" + em | colS ww && rowS wh = cm ++ " and " ++ rm + | colS ww = cm + | rowS wh = rm + | otherwise = "smallMsg: passed correct term size!" + in + "This games requires a display of " ++ show sw ++ + " columns and " ++ show sh ++ " rows.\n" ++ + "Yours only has " ++ em ++ "!\n\n" ++ + "Please resize your terminal and restart the game.\n" + in "DisplayTooSmall.\n" ++ smallMsg tds + +instance MC.Exception ATGException where diff --git a/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object/Narrate.hs b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object/Narrate.hs new file mode 100644 index 0000000..c80f443 --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object/Narrate.hs @@ -0,0 +1,30 @@ +{-# LANGUAGE GeneralizedNewtypeDeriving #-} + +module Terminal.Game.Layer.Object.Narrate where + +-- Narrate Monad, replay on screen from a GRec + +import Terminal.Game.Layer.Object.Interface +import Terminal.Game.Layer.Object.Primitive +import Terminal.Game.Layer.Object.IO () -- MonadIo + +import qualified Control.Monad.Catch as MC +import qualified Control.Monad.State as S +import qualified Control.Monad.Trans as T + + +newtype Narrate a = Narrate (S.StateT GRec IO a) + deriving (Functor, Applicative, Monad, + T.MonadIO, S.MonadState GRec, + MC.MonadThrow, MC.MonadCatch, MC.MonadMask) + +instance MonadInput Narrate where + startEvents fps = T.liftIO $ startEvents fps + pollEvents _ = S.state getPolled + stopEvents ts = T.liftIO $ stopEvents ts + +instance MonadLogic Narrate where + checkQuit _ _ = S.gets isOver + +runReplay :: Narrate a -> GRec -> IO a +runReplay (Narrate s) k = S.evalStateT s k diff --git a/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object/Primitive.hs b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object/Primitive.hs new file mode 100644 index 0000000..5f067d3 --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object/Primitive.hs @@ -0,0 +1,93 @@ +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE LambdaCase #-} + +module Terminal.Game.Layer.Object.Primitive where + +import Terminal.Game.Plane + +import qualified GHC.Generics as G +import qualified Data.ByteString as BS +import qualified Data.Serialize as Z +import qualified Data.Sequence as S +import qualified Test.QuickCheck as Q + +------------------------------------------------------------------------------- +-- Assorted API types + +-- | The number of 'Tick's fed each second to the logic function; +-- constant on every machine. /Frames/ per second might be lower +-- (depending on drawing function onerousness, terminal refresh rate, +-- etc.). +type TPS = Integer + +-- | The number of frames blit to terminal per second. Frames might be +-- dropped, but game speed will remain constant. Check @balls@ +-- (@cabal run -f examples balls@) to see how to display FPS. +-- For obvious reasons (blits would be wasted) @max FPS = TPS@. +type FPS = Integer + +-- | An @Event@ is a 'Tick' (time passes) or a 'KeyPress'. +data Event = Tick + | KeyPress Char + deriving (Show, Eq, G.Generic) +instance Z.Serialize Event where + +instance Q.Arbitrary Event where + arbitrary = Q.oneof [ pure Tick, + KeyPress <$> Q.arbitrary ] + +-- | Game environment with current terminal dimensions and current display +-- rate. +data GEnv = GEnv { eTermDims :: Dimensions, + -- ^ Current terminal dimensions. + eFPS :: FPS + -- ^ Current blitting rate. + } + deriving (Show, Eq) + +------------------------------------------------------------------------------- +-- GRec record/replay game typs + +-- | Opaque data type with recorded game input, for testing purposes. +data GRec = GRec { aPolled :: S.Seq [Event], + -- Seq. of polled events + aTermSize :: S.Seq (Maybe Dimensions) } + -- Seq. of polled termdims + deriving (Show, Eq, G.Generic) +instance Z.Serialize GRec where + +igrec :: GRec +igrec = GRec S.Empty S.Empty + +addDims :: Maybe Dimensions -> GRec -> GRec +addDims mds (GRec p s) = GRec p (mds S.<| s) + +getDims :: GRec -> (Maybe Dimensions, GRec) +getDims (GRec p (ds S.:|> d)) = (d, GRec p ds) +getDims _ = error "getDims: empty Seq" + -- Have to use _ or “non exhaustive patterns” warning + +addPolled :: [Event] -> GRec -> GRec +addPolled es (GRec p s) = GRec (es S.<| p) s + +getPolled :: GRec -> ([Event], GRec) +getPolled (GRec (ps S.:|> p) d) = (p, GRec ps d) +getPolled _ = error "getEvents: empty Seq" + +isOver :: GRec -> Bool +isOver (GRec S.Empty _) = True +isOver _ = False + +-- | Reads a file containing a recorded session. +readRecord :: FilePath -> IO GRec +readRecord fp = Z.decode <$> BS.readFile fp >>= \case + Left e -> error $ "readRecord could not decode: " ++ + show e + Right r -> return r + +-- | Convenience function to create a 'GRec' from screen size (constant) plus a list of events. Useful with 'setupGame'. +createGRec :: Dimensions -> [Event] -> GRec +createGRec ds es = let l = length es * 2 in + GRec (S.fromList [es]) + (S.fromList . replicate l $ Just ds) + diff --git a/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object/Record.hs b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object/Record.hs new file mode 100644 index 0000000..a7c18a2 --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object/Record.hs @@ -0,0 +1,53 @@ +{-# LANGUAGE GeneralizedNewtypeDeriving #-} + +module Terminal.Game.Layer.Object.Record where + +-- Record Monad, for when I need to play the game and record Events +-- (keypresses, ticks, screen size, FPS) to a file. + +import Terminal.Game.Layer.Object.Interface +import Terminal.Game.Layer.Object.Primitive +import Terminal.Game.Layer.Object.IO () + +import qualified Control.Concurrent as CC +import qualified Control.Monad.Catch as MC +import qualified Control.Monad.Reader as R +import qualified Control.Monad.Trans as T -- MonadIO +import qualified Data.ByteString as BS +import qualified Data.Serialize as S + +-- record the key pressed in a game session + +newtype Record a = Record (R.ReaderT (CC.MVar GRec) IO a) + deriving (Functor, Applicative, Monad, + T.MonadIO, R.MonadReader (CC.MVar GRec), + MC.MonadThrow, MC.MonadCatch, MC.MonadMask) + +-- Lifts IO interface, records where necessary +instance MonadInput Record where + startEvents tps = T.liftIO (startEvents tps) + pollEvents ve = T.liftIO (pollEvents ve) >>= \es -> + modMRec addPolled es + stopEvents ts = T.liftIO (stopEvents ts) + +instance MonadDisplay Record where + setupDisplay = T.liftIO setupDisplay + clearDisplay = T.liftIO clearDisplay + displaySize = T.liftIO displaySize >>= \ds -> + modMRec addDims ds + blitPlane mp p = T.liftIO (blitPlane mp p) + shutdownDisplay = T.liftIO shutdownDisplay + +-- logs and passes the value on +modMRec :: (a -> GRec -> GRec) -> a -> Record a +modMRec f a = R.ask >>= \mv -> + let fmv = CC.modifyMVar_ mv (return . f a) in + T.liftIO fmv >> + return a + +runRecord :: Record a -> CC.MVar GRec -> IO a +runRecord (Record r) me = R.runReaderT r me + +writeRec :: FilePath -> CC.MVar GRec -> IO () +writeRec fp vr = CC.readMVar vr >>= \k -> + BS.writeFile fp (S.encode k) diff --git a/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object/Test.hs b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object/Test.hs new file mode 100644 index 0000000..7e263c1 --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Layer/Object/Test.hs @@ -0,0 +1,77 @@ +------------------------------------------------------------------------------- +-- Layer 2 (mockable IO), as per +-- https://www.parsonsmatt.org/2018/03/22/three_layer_haskell_cake.html +-- 2019 Francesco Ariis GPLv3 +------------------------------------------------------------------------------- + +{-# LANGUAGE GeneralizedNewtypeDeriving #-} + +module Terminal.Game.Layer.Object.Test where + +-- Test (pure) MonadGame* typeclass implementation for testing purposes. + +import Terminal.Game.Layer.Object.Interface +import Terminal.Game.Layer.Object.Primitive + +import qualified Control.Monad.RWS as S + + +----------- +-- TYPES -- +----------- + +data TestEvent = TCleanUpError + | TQuitGame + | TSetupDisplay + | TShutdownDisplay + | TStartGame + | TStartEvents + | TStopEvents + deriving (Eq, Show) + +-- r: () +-- w: [TestEvents] +-- s: [GTest] +newtype Test a = Test (S.RWS () [TestEvent] GRec a) + deriving (Functor, Applicative, Monad, + S.MonadState GRec, + S.MonadWriter [TestEvent]) + +runTest :: Test a -> GRec -> (a, [TestEvent]) +runTest (Test m) es = S.evalRWS m () es + + +----------- +-- CLASS -- +----------- + +tconst :: a -> Test a +tconst a = Test $ return a + +mockHandle :: InputHandle +mockHandle = InputHandle (error "mock handle keyMvar") + (error "mock handle threads") + +instance MonadInput Test where + startEvents _ = S.tell [TStartEvents] >> + return mockHandle + pollEvents _ = S.state getPolled + stopEvents _ = S.tell [TStopEvents] + +instance MonadTimer Test where + getTime = return 1 + sleepABit _ = return () + +instance MonadException Test where + cleanUpErr a _ = S.tell [TCleanUpError] >> a + throwExc e = error . show $ e + +instance MonadLogic Test where + checkQuit _ _ = S.gets isOver + +instance MonadDisplay Test where + setupDisplay = () <$ S.tell [TSetupDisplay] + clearDisplay = return () + displaySize = Test $ S.state getDims + blitPlane _ _ = return () + shutdownDisplay = () <$ S.tell [TShutdownDisplay] diff --git a/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Plane.hs b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Plane.hs new file mode 100644 index 0000000..029837b --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Plane.hs @@ -0,0 +1,237 @@ +{-# LANGUAGE DeriveGeneric #-} + +------------------------------------------------------------------------------- +-- Screen datatypes and functions +-- 2017 Francesco Ariis GPLv3 +------------------------------------------------------------------------------- + +-- a canvas where to draw our stuff + +module Terminal.Game.Plane where + +import Terminal.Game.Character + +import qualified Data.Array as A +import qualified Data.Bifunctor as B +import qualified Data.List.Split as LS +import qualified Data.Tuple as T +import qualified GHC.Generics as G +import qualified System.Console.ANSI as CA + + +---------------- +-- DATA TYPES -- +---------------- + +-- | 'Row's and 'Column's are 1-based (top-left position is @1 1@). +type Coords = (Row, Column) +type Row = Int +type Column = Int + +-- | Size of a surface in 'Row's and 'Column's. +type Dimensions = (Width, Height) + +-- | Expressed in 'Column's. +type Width = Int +-- | Expressed in 'Row's. +type Height = Int + +type Bold = Bool +type Reversed = Bool + +-- can be an ASCIIChar or a special, transparent character +data Cell = CellChar Char Bold + Reversed (Maybe (CA.Color, CA.ColorIntensity)) + | Transparent + deriving (Show, Eq, Ord, G.Generic) + -- I found no meaningful speed improvements by making this + -- only w/ 1 constructor. + +-- | A two-dimensional surface (Row, Column) where to blit stuff. +newtype Plane = Plane { fromPlane :: A.Array Coords Cell } + deriving (Show, Eq, G.Generic) + -- Could this be made into an UArray? Nope, since UArray is + -- only instanced on Words, Int, Chars, etc. + +------------------------------------------------------------------------------- +-- Plane interface (abstracting Array) + +listPlane :: Coords -> [Cell] -> Plane +listPlane (r, c) cs = Plane $ A.listArray ((1,1), (r, c)) cs + +-- | Dimensions or a plane. +planeSize :: Plane -> Dimensions +planeSize p = T.swap . snd $ A.bounds (fromPlane p) + +assocsPlane :: Plane -> [(Coords, Cell)] +assocsPlane p = A.assocs (fromPlane p) + +elemsPlane :: Plane -> [Cell] +elemsPlane p = A.elems (fromPlane p) + +-- Array.// +updatePlane :: Plane -> [(Coords, Cell)] -> Plane +updatePlane (Plane a) kcs = Plane $ a A.// kcs + +-- faux map +mapPlane :: (Cell -> Cell) -> Plane -> Plane +mapPlane f (Plane a) = Plane $ fmap f a + + +---------- +-- CREA -- +---------- + +creaCell :: Char -> Cell +creaCell ch = CellChar chm False False Nothing + where + chm = win32SafeChar ch + +colorCell :: CA.Color -> CA.ColorIntensity -> Cell -> Cell +colorCell k i (CellChar c b r _) = CellChar c b r (Just (k, i)) +colorCell _ _ Transparent = Transparent + +boldCell :: Cell -> Cell +boldCell (CellChar c _ r k) = CellChar c True r k +boldCell Transparent = Transparent + +reverseCell :: Cell -> Cell +reverseCell (CellChar c b _ k) = CellChar c b True k +reverseCell Transparent = Transparent + +-- | Creates 'Plane' from 'String', good way to import ASCII +-- art/diagrams. @error@s on empty string. +stringPlane :: String -> Plane +stringPlane t = stringPlaneGeneric Nothing t + +-- | Same as 'stringPlane', but with transparent 'Char'. +-- @error@s on empty string. +stringPlaneTrans :: Char -> String -> Plane +stringPlaneTrans c t = stringPlaneGeneric (Just c) t + +-- | Creates an empty, opaque 'Plane'. +blankPlane :: Width -> Height -> Plane +blankPlane w h = listPlane (h, w) (repeat $ creaCell ' ') + +-- | Adds transparency to a plane, matching a given character +makeTransparent :: Char -> Plane -> Plane +makeTransparent tc p = mapPlane f p + where + f cl | cellChar cl == tc = Transparent + | otherwise = cl + +-- | Changes every transparent cell in the 'Plane' to an opaque @' '@ +-- character. +makeOpaque :: Plane -> Plane +makeOpaque p = let (w, h) = planeSize p + in pastePlane p (blankPlane w h) (1, 1) + + + +----------- +-- SLICE -- +----------- + +-- | Paste one plane over the other at a certain position (p1 gets over p2). +pastePlane :: Plane -> Plane -> Coords -> Plane +pastePlane p1 p2 (r, c) + | r > h2 || c > w2 = p2 + | otherwise = + let ks = assocsPlane p1 + fs = filter (\x -> solid x && inside x) ks + ts = fmap (B.first trasl) fs + in updatePlane p2 ts + where + trasl :: Coords -> Coords + trasl (wr, wc) = (wr + r - 1, wc + c - 1) + + -- inside new position, cheaper than first mapping and then + -- filtering. + inside (wcs, _) = + let (r1', c1') = trasl wcs + in r1' >= 1 && r1' <= h2 && + c1' >= 1 && c1' <= w2 + + solid (_, Transparent) = False + solid _ = True + + (w2, h2) = planeSize p2 + +-- | Cut out a plane by top-left and bottom-right coordinates. +subPlane :: Plane -> Coords -> Coords -> Plane +subPlane p (r1, c1) (r2, c2) + | r1 > r2 || c1 > c2 = err (r1, c1) (r2, c2) + | otherwise = + let cs = assocsPlane p + fs = filter f cs + (pw, ph) = planeSize p + (w, h) = (min pw (c2-c1+1), min ph (r2-r1+1)) + in listPlane (h, w) (map snd fs) + where + f ((rw, cw), _) = rw >= r1 && rw <= r2 && + cw >= c1 && cw <= c2 + + err p1 p2 = error ("subPlane: top-left point " ++ show p1 ++ + " > bottom-right point " ++ show p2 ++ ".") + +------------- +-- INQUIRE -- +------------- + +cellChar :: Cell -> Char +cellChar (CellChar c _ _ _) = c +cellChar Transparent = ' ' + +cellColor :: Cell -> Maybe (CA.Color, CA.ColorIntensity) +cellColor (CellChar _ _ _ k) = k +cellColor Transparent = Nothing + +isBold :: Cell -> Bool +isBold (CellChar _ b _ _) = b +isBold _ = False + +isReversed :: Cell -> Bool +isReversed (CellChar _ _ r _) = r +isReversed _ = False + +-- | A String (@\n@ divided and ended) representing the 'Plane'. Useful +-- for debugging/testing purposes. +planePaper :: Plane -> String +planePaper p = unlines . LS.chunksOf w . map cellChar $ elemsPlane p + where + w :: Int + w = fromIntegral . fst . planeSize $ p + +----------------- +-- ANCILLARIES -- +----------------- + +stringPlaneGeneric :: Maybe Char -> String -> Plane +stringPlaneGeneric _ "" = makeTransparent ' ' (blankPlane 1 1) +stringPlaneGeneric mc t = vitrous + where + lined = lines t + + h :: Int + h = length lined + + w :: Int + w = maximum (map length lined) + + pad :: Int -> String -> String + pad mw tl = take mw (tl ++ repeat ' ') + + padded :: [String] + padded = map (pad w) lined + + celled :: [Cell] + celled = map creaCell . concat $ padded + + plane :: Plane + plane = listPlane (h, w) celled + + vitrous :: Plane + vitrous = case mc of + Just c -> makeTransparent c plane + Nothing -> plane + diff --git a/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Random.hs b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Random.hs new file mode 100644 index 0000000..288ed73 --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Random.hs @@ -0,0 +1,20 @@ +module Terminal.Game.Random ( R.StdGen, + R.UniformRange, + R.getStdGen, + R.mkStdGen, + getRandom, + pickRandom ) + where + +import System.Random as R + + +-- | Simple, pure pseudo-random generator. +getRandom :: UniformRange a => (a, a) -> StdGen -> (a, StdGen) +getRandom bs sg = uniformR bs sg + +-- | Picks at random from list. +pickRandom :: [a] -> StdGen -> (a, StdGen) +pickRandom as sg = let l = length as + (a, sg') = getRandom (0, l-1) sg + in (as !! a, sg') diff --git a/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Timer.hs b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Timer.hs new file mode 100644 index 0000000..ff4c2aa --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/src/Terminal/Game/Timer.hs @@ -0,0 +1,3 @@ +module Terminal.Game.Timer (module T) where + +import Control.Timer.Tick as T diff --git a/vendored/ansi-terminal-game-1.8.0.0/test/Terminal/Game/DrawSpec.hs b/vendored/ansi-terminal-game-1.8.0.0/test/Terminal/Game/DrawSpec.hs new file mode 100644 index 0000000..94b2d9c --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/test/Terminal/Game/DrawSpec.hs @@ -0,0 +1,68 @@ +module Terminal.Game.DrawSpec where + +import Test.Hspec +import Terminal.Game.Plane +import Terminal.Game.Draw +import Terminal.Game -- language hyphenators + + +spec :: Spec +spec = do + + describe "mergePlanes" $ do + it "piles multiple planes together" $ + mergePlanes (stringPlane "aa") + [((1,2), cell 'b')] `shouldBe` stringPlane "ab" + it "works in the middle too" $ + mergePlanes (stringPlane "aaa\naaa\naaa") + [((2,2), cell 'b')] `shouldBe` + stringPlane "aaa\naba\naaa" + + describe "textBox/textBoxLiquid" $ do + let s = "las rana in Spa" + w = 6 + ps = textBox w 2 s + pl = textBoxLiquid w s + it "textBox follows specific size" $ + planeSize ps `shouldBe` (6, 2) + it "textBoxLiquid fits the whole string" $ + planeSize pl `shouldBe` (6, 3) + it "textBox should make a transparent plane" $ + let p1 = textBox 6 1 "a c e " + p2 = textBox 6 1 " b d f" + pc = p1 & (1, 1) % p2 + in planePaper pc `shouldBe` "abcdef\n" + + describe "textBoxHypen" $ do + let tbh = textBoxHyphen spanish 8 2 "Con pianito" + it "hyphens long words" $ + planePaper tbh `shouldSatisfy` elem '-' + + describe "***" $ do + let a = stringPlane ".\n.\n.\n" + b = stringPlane "*" + c = stringPlane ".\n*\n.\n" + it "blits b in the centre of a" $ + a *** b `shouldBe` c + + -- combinators + + let sp = stringPlane "ab" + bp = blankPlane 4 3 + + describe "%^>" $ do + it "blits in the top right corner" $ + planePaper (bp & (1,1) %^> sp) `shouldBe` " ab\n \n \n" + + describe "%_<" $ do + it "blits in the bottom left corner" $ + planePaper (bp & (2,1) %.< sp) `shouldBe` " \nab \n \n" + + describe "%_<" $ do + it "blits in the bottom left corner" $ + planePaper (bp & (2,3) %.> sp) `shouldBe` " \nab \n \n" + + describe "%" $ do + it "mixes with alternative combinators" $ + planePaper (bp & (1,2) % sp & (2,3) %.> sp) `shouldBe` + " ab \nab \n \n" diff --git a/vendored/ansi-terminal-game-1.8.0.0/test/Terminal/Game/Layer/ImperativeSpec.hs b/vendored/ansi-terminal-game-1.8.0.0/test/Terminal/Game/Layer/ImperativeSpec.hs new file mode 100644 index 0000000..3599cfe --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/test/Terminal/Game/Layer/ImperativeSpec.hs @@ -0,0 +1,54 @@ +module Terminal.Game.Layer.ImperativeSpec where + +import Terminal.Game.Layer.Imperative +import Terminal.Game.Layer.Object +import Terminal.Game.Random +import Alone +import Balls + +import Test.Hspec +import Test.Hspec.QuickCheck + +import qualified Test.QuickCheck as Q + +spec :: Spec +spec = do + + describe "runGame" $ do + let nd = error "" + s :: (Integer, Bool, Integer) + s = (0, False, 0) + lf (t, True, i) Tick = (t+1, True, i+1) + lf (t, b, i) Tick = (t+1, b, i ) + lf (t, _, i) (KeyPress _) = (t, True, i ) + qf (3, _, _) = True + qf _ = False + es = [Tick, KeyPress 'c', KeyPress 'c', Tick, Tick] + g = Game nd s (const lf) nd qf + it "does not confuse input and logic" $ + testGame g (createGRec (80, 24) es) `shouldBe` (3, True, 2) + + describe "testGame" $ do + it "tests a game" $ do + r <- readRecord "test/records/alone-record-test.gr" + testGame aloneInARoom r `shouldBe` MyState (20, 66) Stop True + it "picks up screen resize events" $ do + r <- readRecord "test/records/balls-dims.gr" + let g = fireworks (mkStdGen 1) + t = testGame g r + length (balls t) `shouldBe` 1 + it "picks up screen resize events" $ do + r <- readRecord "test/records/balls-slow.gr" + let g = fireworks (mkStdGen 1) + t = testGame g r + bslow t `shouldBe` True + it "does not hang on empty/unclosed input" $ + let w = createGRec (80, 24) [Tick] in + testGame aloneInARoom w `shouldBe` MyState (10, 10) Stop False + modifyMaxSize (const 1000) $ + it "does not crash/hang on random input" $ Q.property $ + let genEvs = Q.listOf1 Q.arbitrary + in Q.forAll genEvs $ + \es -> let w = createGRec (80, 24) es + a = testGame aloneInARoom w + in a == a diff --git a/vendored/ansi-terminal-game-1.8.0.0/test/Terminal/Game/PlaneSpec.hs b/vendored/ansi-terminal-game-1.8.0.0/test/Terminal/Game/PlaneSpec.hs new file mode 100644 index 0000000..7fde01e --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/test/Terminal/Game/PlaneSpec.hs @@ -0,0 +1,59 @@ +module Terminal.Game.PlaneSpec where + +import Test.Hspec +import Terminal.Game.Plane +import Terminal.Game.Draw + +import qualified Control.Exception as E + + +spec :: Spec +spec = do + + let testPlane = blankPlane 2 2 & + (1,1) % box 2 2 '.' & + (1,2) % cell ' ' + + describe "listPlane" $ do + it "creates a plane from string" $ + listPlane (2,2) (map creaCell ". ..") `shouldBe` testPlane + it "ignores extra characters" $ + listPlane (2,2) (map creaCell ". ..abc") `shouldBe` testPlane + + describe "pastePlane" $ do + it "pastes a simple plane onto another" $ + pastePlane (cell 'a') (cell 'b') (1,1) `shouldBe` cell 'a' + + describe "stringPlane" $ do + it "creates plane from spec" $ + stringPlane ".\n.." `shouldBe` testPlane + + describe "stringPlaneTrans" $ do + it "allows transparency" $ + stringPlaneTrans '.' ".\n.." `shouldBe` makeTransparent '.' testPlane + + describe "updatePlane" $ do + let ma = listPlane (2,1) (map creaCell "ab") + mb = listPlane (2,1) (map creaCell "xb") + it "updates a Plane" $ + updatePlane ma [((1,1), creaCell 'x')] `shouldBe` mb + + describe "subPlane" $ do + let pa = word "prova" === word "fol" + it "cuts out a plane" $ + planePaper (subPlane pa (1, 1) (2, 1)) `shouldBe` "p\nf\n" + it "does not crash on OOB" $ + planeSize (subPlane pa (1, 1) (10, 10)) `shouldBe` (5, 2) + it "errs on emptycell" $ + E.evaluate (subPlane pa (2, 3) (1, 1)) `shouldThrow` + errorCall "subPlane: top-left point (2,3) > bottom-right point (1,1)." + it "but not on a single cell" $ + subPlane pa (2, 3) (2, 3) `shouldBe` cell 'l' + + describe "hcat/vcat" $ do + let pa = blankPlane 2 1 + pb = blankPlane 3 4 + it "concats planes horizontally with hcat" $ + planeSize (hcat [pa, pb]) `shouldBe` (5, 4) + it "concats planes horizontally with vcat" $ + planeSize (vcat [pa, pb]) `shouldBe` (3, 5) diff --git a/vendored/ansi-terminal-game-1.8.0.0/test/Terminal/Game/RandomSpec.hs b/vendored/ansi-terminal-game-1.8.0.0/test/Terminal/Game/RandomSpec.hs new file mode 100644 index 0000000..f4b8bdf --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/test/Terminal/Game/RandomSpec.hs @@ -0,0 +1,20 @@ +module Terminal.Game.RandomSpec where + +import Test.Hspec +import Test.Hspec.QuickCheck +import Terminal.Game.Random + + +spec :: Spec +spec = do + + describe "pickRandom" $ do + prop "picks items at random from a list" $ + \i -> let g = mkStdGen i + in fst (pickRandom ['a', 'b'] g) /= 'c' + prop "does not exclude any item" $ + \i -> let g = mkStdGen i + rf tg = pickRandom [1,2] tg + rs = iterate (\(_, lg') -> rf lg') (rf g) + ts = take 100 rs + in sum (map fst ts) /= length ts diff --git a/vendored/ansi-terminal-game-1.8.0.0/test/Test.hs b/vendored/ansi-terminal-game-1.8.0.0/test/Test.hs new file mode 100644 index 0000000..a824f8c --- /dev/null +++ b/vendored/ansi-terminal-game-1.8.0.0/test/Test.hs @@ -0,0 +1 @@ +{-# OPTIONS_GHC -F -pgmF hspec-discover #-} diff --git a/vendored/ansi-terminal-game-1.8.0.0/test/records/alone-record-test.gr b/vendored/ansi-terminal-game-1.8.0.0/test/records/alone-record-test.gr new file mode 100644 index 0000000..caf7372 Binary files /dev/null and b/vendored/ansi-terminal-game-1.8.0.0/test/records/alone-record-test.gr differ diff --git a/vendored/ansi-terminal-game-1.8.0.0/test/records/balls-dims.gr b/vendored/ansi-terminal-game-1.8.0.0/test/records/balls-dims.gr new file mode 100644 index 0000000..c533c97 Binary files /dev/null and b/vendored/ansi-terminal-game-1.8.0.0/test/records/balls-dims.gr differ diff --git a/vendored/ansi-terminal-game-1.8.0.0/test/records/balls-slow.gr b/vendored/ansi-terminal-game-1.8.0.0/test/records/balls-slow.gr new file mode 100644 index 0000000..34dfde8 Binary files /dev/null and b/vendored/ansi-terminal-game-1.8.0.0/test/records/balls-slow.gr differ