pax_global_header00006660000000000000000000000064123663467500014526gustar00rootroot0000000000000052 comment=2d9ee72c003c2b9b03976fa14fd2db8994fc0abb nSnake-3.0.1/000077500000000000000000000000001236634675000127465ustar00rootroot00000000000000nSnake-3.0.1/.gitignore000066400000000000000000000001131236634675000147310ustar00rootroot00000000000000 # Object files and binary bin/* *.o # local documentation doc/html tmp/ nSnake-3.0.1/AUTHORS000066400000000000000000000007371236634675000140250ustar00rootroot00000000000000Alexandre Dantas * Main upstream and current maintainer. Christopher Meng * Made nSnake available on Fedora. * Warned about man page errors. * Improved Makefile standards. Olof Johansson * Added vim directional key-bindings. * Fixed high-score reset between turns. * Fixed Makefile issues. Lingyu Zhu * Fixed bug on random number generator. Dylan * Fixed linker errors. nSnake-3.0.1/BUGS000066400000000000000000000002211236634675000134240ustar00rootroot00000000000000BUGS ==== There are no long-term bugs. For a list of currently unsolved issues, go to: https://github.com/alexdantas/nsnake/issues?state=open nSnake-3.0.1/COPYING000066400000000000000000001045131236634675000140050ustar00rootroot00000000000000 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 . nSnake-3.0.1/ChangeLog000066400000000000000000000117371236634675000145310ustar00rootroot0000000000000007-28-2014 v3.0.0 * Gameplay: Levels! Now you can make your own maps and mazes that the snake will play on. As expected, each level has it's own score count. * Gameplay: Made possible for the game levels to scroll. Now you can set the game map to move on all 4 directions under a configurable speed. * Interface: Made subtle changes on the GUI; specially on the text dialogs and menu items. * Development: Rewrote score files format from scratch. It is now way more extensible; way better than a mere binary blob. * Development: Tons and tons of bug fixes. Namely random game crashes. * Interface: Game now ships with menu and icon files. It gets installed in a way that shows nSnake on the menus of most Linux distributions. 05-29-2014 v2.0.5 * Development: Completely removed any C++11 features from the codebase. Game is now portable across old compilers. * Bug: Fixed level-generating bug that made the player unable to move at all. * Development: Instead of providing `iniparser` we rely on the user having it installed on the system. 03-06-2014 v2.0.0 * Development: Completely rewrote the game in C++, using brand new C++11 features! * Interface: Main menu has a cute Matrix-like animation. * Interface: GUI-like menus and buttons and widgets and much more. * Gameplay: Made input keys customizable. * Gameplay: Game profiles - high scores are made in a player-by-player basis. 08-08-2013 v1.7 * Abandoned: nsnake is now deprecated in favor of "nsnake++", the new master branch on git. The old version will still be kept on the "nsnake-classic" branch. 05-14-2013 v1.7 * Development: High-score file stays on the user's home directory. * Gameplay: vim-like control keys (hjkl). * Interface: support for terminals that doesn't have colors. * Bug: fixed minor issues on man page and makefile; see AUTHORS file. * Gameplay: Smoothed gameplay and screen refresh controls. * Development: now using Git for version control. 11-01-2012 v1.5 * Development: Now nsnake uses GNU getopt_long! See files arguments.c/.h for info. * Development: The main documentation page has a separate file now. * Development: Cleaned a lot the source files and reformatted pretty much everything. * Development: Created manpage! Get help with nsnake will be as easy as 'man nsnake'. * Bug: Fixed another Highscore bug; fully functional now. * Development: scores.c/.h now is hscores.c/.h * Development: Now nsnake saves it's high scores in /var/games! And it now runs at setgid root:games. 12-04-2011 v1.3 * Added: arrow keys control over the speed levels at the main menu. * Added: Clean the screen when the user quits the game. * Separated the installation info from README to INSTALL * Fixed: Major memory leaks! * Cleaned the source code: function names now begin with the name of the module in which they are made. * Successfuly documented every function, variable and data structure on the whole source code. Speaking of this, when you type 'make dox', it creates a link to the whole documentation under /doc. * Changed: No mode 'Normal Mode' or 'Teleport mode'. It's now 'Borders On' and 'Borders Off' * Added: Option to return to main menu at the Game Over screen. * Added: Commandline option to display the GPLv3 preamble and Warranty informations. 11-24-2011 v1.0 * Interface: Now the main menu is prettier than ever! Also the teleport mode! * Gameplay: Added speed levels! Now the user can select which level will the game start on the main menu * Bug: Bug that allowed the snake to go offscreen. * Bug: Bug that allowed a fruit start offscreen. * Bug: Highscore bug! 11-19-2011 v0.8.5: * Development: Dinamic memory allocation! No more [1000] arrays! * Development: Licensed the software to GNU GPL v3 * Development: Greatly cleaned up the source code. Arranged the functions by name and such. 11-17-2011 v0.8: * Development: Added scores.h and scores.c * Gameplay: Added support to change snake speed * Bug: Not opening scores.bin doesn't crash the game now. * Development: Cleaned and documented a lot of the code 11-16-2011 v0.7.8: * Development: Greatly improved the Makefile, added the dist and backup options * Development Improved the installation process. Now we have support for the standard make targets. 11-03-2011 v0.7.4: * Development: Improved Makefile, added the VERBOSE (V) option * Gameplay: Added the (beta) Highscore support * Interface: Improved the GUI * Gameplay: The control is now by the arrow keys - no longer `WASD` * Interface: Added two command-line interfaces '--help and --version' 09-10-2011 v0.5 * Initial release: game works. nSnake-3.0.1/Doxyfile000066400000000000000000000252171236634675000144630ustar00rootroot00000000000000# Doxyfile 1.8.7 #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = nsnake PROJECT_NUMBER = PROJECT_BRIEF = "Classic snake game for the terminal" PROJECT_LOGO = OUTPUT_DIRECTORY = ./doc CREATE_SUBDIRS = NO ALLOW_UNICODE_NAMES = NO OUTPUT_LANGUAGE = English BRIEF_MEMBER_DESC = YES REPEAT_BRIEF = YES ABBREVIATE_BRIEF = ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = NO FULL_PATH_NAMES = YES STRIP_FROM_PATH = STRIP_FROM_INC_PATH = SHORT_NAMES = NO JAVADOC_AUTOBRIEF = YES QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO INHERIT_DOCS = YES SEPARATE_MEMBER_PAGES = NO TAB_SIZE = 4 ALIASES = TCL_SUBST = OPTIMIZE_OUTPUT_FOR_C = NO OPTIMIZE_OUTPUT_JAVA = NO OPTIMIZE_FOR_FORTRAN = NO OPTIMIZE_OUTPUT_VHDL = NO EXTENSION_MAPPING = MARKDOWN_SUPPORT = YES AUTOLINK_SUPPORT = YES BUILTIN_STL_SUPPORT = NO CPP_CLI_SUPPORT = NO SIP_SUPPORT = NO IDL_PROPERTY_SUPPORT = YES DISTRIBUTE_GROUP_DOC = NO SUBGROUPING = YES INLINE_GROUPED_CLASSES = NO INLINE_SIMPLE_STRUCTS = NO TYPEDEF_HIDES_STRUCT = YES LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- EXTRACT_ALL = NO EXTRACT_PRIVATE = NO EXTRACT_PACKAGE = NO EXTRACT_STATIC = NO EXTRACT_LOCAL_CLASSES = YES EXTRACT_LOCAL_METHODS = NO EXTRACT_ANON_NSPACES = NO HIDE_UNDOC_MEMBERS = NO HIDE_UNDOC_CLASSES = NO HIDE_FRIEND_COMPOUNDS = NO HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO CASE_SENSE_NAMES = YES HIDE_SCOPE_NAMES = NO SHOW_INCLUDE_FILES = YES SHOW_GROUPED_MEMB_INC = NO FORCE_LOCAL_INCLUDES = NO INLINE_INFO = YES SORT_MEMBER_DOCS = YES SORT_BRIEF_DOCS = NO SORT_MEMBERS_CTORS_1ST = NO SORT_GROUP_NAMES = NO SORT_BY_SCOPE_NAME = NO STRICT_PROTO_MATCHING = NO GENERATE_TODOLIST = YES GENERATE_TESTLIST = YES GENERATE_BUGLIST = YES GENERATE_DEPRECATEDLIST= YES ENABLED_SECTIONS = MAX_INITIALIZER_LINES = 30 SHOW_USED_FILES = YES SHOW_FILES = YES SHOW_NAMESPACES = YES FILE_VERSION_FILTER = LAYOUT_FILE = CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- QUIET = NO WARNINGS = YES WARN_IF_UNDOCUMENTED = YES WARN_IF_DOC_ERROR = YES WARN_NO_PARAMDOC = NO WARN_FORMAT = "$file:$line: $text" WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- INPUT = ./src/ \ ./doc/doxygen_mainpage.hpp INPUT_ENCODING = UTF-8 FILE_PATTERNS = *.cpp \ *.hpp RECURSIVE = YES EXCLUDE = EXCLUDE_SYMLINKS = NO EXCLUDE_PATTERNS = EXCLUDE_SYMBOLS = EXAMPLE_PATH = EXAMPLE_PATTERNS = EXAMPLE_RECURSIVE = NO IMAGE_PATH = INPUT_FILTER = FILTER_PATTERNS = FILTER_SOURCE_FILES = NO FILTER_SOURCE_PATTERNS = USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- SOURCE_BROWSER = YES INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES REFERENCED_BY_RELATION = NO REFERENCES_RELATION = NO REFERENCES_LINK_SOURCE = YES SOURCE_TOOLTIPS = YES USE_HTAGS = NO VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- ALPHABETICAL_INDEX = YES COLS_IN_ALPHA_INDEX = 5 IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- GENERATE_HTML = YES HTML_OUTPUT = html HTML_FILE_EXTENSION = .html HTML_HEADER = HTML_FOOTER = HTML_STYLESHEET = HTML_EXTRA_STYLESHEET = HTML_EXTRA_FILES = HTML_COLORSTYLE_HUE = 220 HTML_COLORSTYLE_SAT = 100 HTML_COLORSTYLE_GAMMA = 80 HTML_TIMESTAMP = YES HTML_DYNAMIC_SECTIONS = NO HTML_INDEX_NUM_ENTRIES = 100 GENERATE_DOCSET = NO DOCSET_FEEDNAME = "Doxygen generated docs" DOCSET_BUNDLE_ID = org.doxygen.Project DOCSET_PUBLISHER_ID = org.doxygen.Publisher DOCSET_PUBLISHER_NAME = Publisher GENERATE_HTMLHELP = NO CHM_FILE = HHC_LOCATION = GENERATE_CHI = NO CHM_INDEX_ENCODING = BINARY_TOC = NO TOC_EXPAND = NO GENERATE_QHP = NO QCH_FILE = QHP_NAMESPACE = org.doxygen.Project QHP_VIRTUAL_FOLDER = doc QHP_CUST_FILTER_NAME = QHP_CUST_FILTER_ATTRS = QHP_SECT_FILTER_ATTRS = QHG_LOCATION = GENERATE_ECLIPSEHELP = NO ECLIPSE_DOC_ID = org.doxygen.Project DISABLE_INDEX = NO GENERATE_TREEVIEW = NO ENUM_VALUES_PER_LINE = 4 TREEVIEW_WIDTH = 250 EXT_LINKS_IN_WINDOW = NO FORMULA_FONTSIZE = 10 FORMULA_TRANSPARENT = YES USE_MATHJAX = NO MATHJAX_FORMAT = HTML-CSS MATHJAX_RELPATH = http://www.mathjax.org/mathjax MATHJAX_EXTENSIONS = MATHJAX_CODEFILE = SEARCHENGINE = YES SERVER_BASED_SEARCH = NO EXTERNAL_SEARCH = NO SEARCHENGINE_URL = SEARCHDATA_FILE = searchdata.xml EXTERNAL_SEARCH_ID = EXTRA_SEARCH_MAPPINGS = #--------------------------------------------------------------------------- # Configuration options related to the LaTeX output #--------------------------------------------------------------------------- GENERATE_LATEX = NO LATEX_OUTPUT = latex LATEX_CMD_NAME = latex MAKEINDEX_CMD_NAME = makeindex COMPACT_LATEX = NO PAPER_TYPE = a4 EXTRA_PACKAGES = LATEX_HEADER = LATEX_FOOTER = LATEX_EXTRA_FILES = PDF_HYPERLINKS = YES USE_PDFLATEX = YES LATEX_BATCHMODE = NO LATEX_HIDE_INDICES = NO LATEX_SOURCE_CODE = NO LATEX_BIB_STYLE = plain #--------------------------------------------------------------------------- # Configuration options related to the RTF output #--------------------------------------------------------------------------- GENERATE_RTF = NO RTF_OUTPUT = rtf COMPACT_RTF = NO RTF_HYPERLINKS = NO RTF_STYLESHEET_FILE = RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # Configuration options related to the man page output #--------------------------------------------------------------------------- GENERATE_MAN = NO MAN_OUTPUT = man MAN_EXTENSION = .3 MAN_SUBDIR = MAN_LINKS = NO #--------------------------------------------------------------------------- # Configuration options related to the XML output #--------------------------------------------------------------------------- GENERATE_XML = NO XML_OUTPUT = xml XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # Configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- GENERATE_DOCBOOK = NO DOCBOOK_OUTPUT = docbook #--------------------------------------------------------------------------- # Configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # Configuration options related to the Perl module output #--------------------------------------------------------------------------- GENERATE_PERLMOD = NO PERLMOD_LATEX = NO PERLMOD_PRETTY = YES PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- ENABLE_PREPROCESSING = YES MACRO_EXPANSION = NO EXPAND_ONLY_PREDEF = NO SEARCH_INCLUDES = YES INCLUDE_PATH = INCLUDE_FILE_PATTERNS = PREDEFINED = EXPAND_AS_DEFINED = SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration options related to external references #--------------------------------------------------------------------------- TAGFILES = GENERATE_TAGFILE = ALLEXTERNALS = NO EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- CLASS_DIAGRAMS = YES MSCGEN_PATH = DIA_PATH = HIDE_UNDOC_RELATIONS = YES HAVE_DOT = YES DOT_NUM_THREADS = 0 DOT_FONTNAME = Helvetica DOT_FONTSIZE = 10 DOT_FONTPATH = CLASS_GRAPH = YES COLLABORATION_GRAPH = YES GROUP_GRAPHS = YES UML_LOOK = NO UML_LIMIT_NUM_FIELDS = 10 TEMPLATE_RELATIONS = NO INCLUDE_GRAPH = YES INCLUDED_BY_GRAPH = YES CALL_GRAPH = NO CALLER_GRAPH = NO GRAPHICAL_HIERARCHY = YES DIRECTORY_GRAPH = YES DOT_IMAGE_FORMAT = png INTERACTIVE_SVG = NO DOT_PATH = DOTFILE_DIRS = MSCFILE_DIRS = DIAFILE_DIRS = DOT_GRAPH_MAX_NODES = 50 MAX_DOT_GRAPH_DEPTH = 0 DOT_TRANSPARENT = NO DOT_MULTI_TARGETS = YES GENERATE_LEGEND = YES DOT_CLEANUP = YES nSnake-3.0.1/INSTALL.md000066400000000000000000000047161236634675000144060ustar00rootroot00000000000000# nsnake INSTALL Installing information for the game nsnake. This applies to GNU/Linux systems. Microsoft Windows users have a precompiled executable. # Installation Briefly, the following shell commands should configure, build, and install this package on the default directories: $ make $ make install # Installation Info By default, `make install` installs the package on the following directories: | Directory | What to store | | ---------------------------- | -------------------------| | `/usr/games/` | Executable files | | `/usr/local/share/man/man6/` | Man page | | `~/.local/share/nsnake/` | User config and scores | # Advanced Installation You can specify a custom root directory for the installation (other than `/`). To do this, give `make install` the option `DESTDIR=CUSTOM_ROOT`, where `CUSTOM_ROOT` must be an absolute directory name. For example: $ make install DESTDIR=test/dir This way, the file hierarchy will be: | Directory | What to store | | ------------------------------------ | -------------------------| | `test/dir/usr/games/` | Executable files | | `test/dir/usr/local/share/man/man6/` | Man page | | `~/.local/share/nsnake/` | User config and scores | You can also specify an installation prefix other than `/usr/local` by giving `make` the option `PREFIX=CUSTOM_PREFIX`, where `CUSTOM_PREFIX` is an absolute directory name. For example: $ make install PREFIX=test/dir This way, the file hierarchy will be: | Directory | What to store | | -------------------------- | -------------------------| | `test/dir/` | Executable files | | `test/dir/share/man/man6/` | Man page | | `~/.local/share/nsnake/` | User config and scores | # Uninstallation To uninstall this package: $ make uninstall To purge (also remove the Highscore files, man page and configurations) go for: $ make purge # Advanced Uninstallation If you specified a custom root directory for installation, `make` needs to know about it to uninstall properly: $ make uninstall DESTDIR=test/dir The same logic applies to purging: $ make purge DESTDIR=test/dir If you installed it with a custom prefix, do the same: $ make uninstall PREFIX=test/dir $ make purge PREFIX=test/dir $ make purge SCOREDIR=test/dir nSnake-3.0.1/Makefile000066400000000000000000000132671236634675000144170ustar00rootroot00000000000000# nsnake Makefile # (2013) Alexandre Dantas # # This is a rather complex Makefile, sorry about that. # It supports the following targets: # # make all Builds the package # make run Builds and runs the program # make install Installs the package on your system # make uninstall Uninstalls the package from your system # make clean Cleans results of building process # make dist Creates source code "tarball" # make doc Generates the documentation with doxygen # make docclean Removes the documentation # # Also, the following commandline arguments customize # default actions: # # V Verbose mode, off by default. # To turn on for the current command, # add `V=1` when calling `make`. # To turn on permanently, uncomment the line # specified below # DESTDIR Installs the package on a custom root directory # (other than `/`). For example `DESTDIR=~/`. # PREFIX Installs the package on a custom directory # (overwrites root) # CFLAGS Changes the C flags used on compilation # CDEBUG If you wish to build on debug mode, add 'CDEBUG=-g' # CFLAGS_PLATFORM # User specified compiler flags # LDFLAGS_PLATFORM # User specified linker flags # # Uncomment line below to tun on verbose mode permanently #V = 1; # General Info PACKAGE = nsnake VERSION = 3.0.0 DATE = $(shell date "+%b%Y") # Install dirs PREFIX = /usr EXEC_PREFIX = $(PREFIX) DATAROOTDIR = $(PREFIX)/share BINDIR = $(EXEC_PREFIX)/bin # Misc stuff PNGDIR = $(DATAROOTDIR)/icons/hicolor XPMDIR = $(DATAROOTDIR)/pixmaps DESKTOPDIR = $(DATAROOTDIR)/applications LEVELDIR = $(DATAROOTDIR)/games/nsnake/levels # Things for the man page MANROOT = $(DATAROOTDIR)/man MANDIR = $(MANROOT)/man$(MANNUMBER) MANNUMBER = 6 MANFILE = $(PACKAGE).$(MANNUMBER) MANPAGE = doc/man/$(MANFILE) # Build info EXE = $(PACKAGE) CDEBUG = -O2 CXXFLAGS = $(CDEBUG) -Wall -Wextra $(CFLAGS_PLATFORM) LDFLAGS = -lncurses $(LDFLAGS_PLATFORM) INCLUDESDIR = -I"src/" -I"deps/" LIBSDIR = # All source files CFILES = $(shell find src -type f -name '*.c') CXXFILES = $(shell find src -type f -name '*.cpp') OBJECTS = $(CFILES:.c=.o) \ $(CXXFILES:.cpp=.o) DEFINES = -DVERSION=\""$(VERSION)"\" \ -DPACKAGE=\""$(PACKAGE)"\" \ -DDATE=\""$(DATE)"\" \ -DSYSTEM_LEVEL_DIR=\""$(LEVELDIR)"\" # commander stuff COMMANDERDIR = deps/commander COMMANDER_CFLAGS = -O2 -Wall -Wextra $(CFLAGS_PLATFORM) COMMANDER_OBJS = $(COMMANDERDIR)/commander.o # Distribution tarball TARNAME = $(PACKAGE) DISTDIR = $(TARNAME)-$(VERSION) # Verbose mode check ifdef V MUTE = VTAG = -v else MUTE = @ endif ifdef DESTDIR ROOT = - else ROOT = endif ifdef DEBUG CDEBUG = -D_NSNAKE_DEBUG else CDEBUG = endif # Make targets all: dirs $(EXE) # Build successful! install: all # Installing... $(MUTE)install -pdm755 $(DESTDIR)$(BINDIR) $(MUTE)install -pm755 bin/$(EXE) $(DESTDIR)$(BINDIR) -$(MUTE)cat $(MANPAGE) | sed -e "s|DATE|$(DATE)|g" -e "s|VERSION|$(VERSION)|g" >$(MANFILE) $(MUTE)install -pdm755 $(DESTDIR)$(MANDIR) $(MUTE)install -pm644 $(MANFILE) $(DESTDIR)$(MANDIR) $(MUTE)rm -f $(MANFILE) $(MUTE)install -pdm755 $(DESTDIR)$(LEVELDIR) $(MUTE)install -pm644 levels/* $(DESTDIR)$(LEVELDIR) $(MUTE)install -pdm755 $(DESTDIR)$(PNGDIR)/16x16/apps/ $(MUTE)install -pm644 misc/nsnake16.png $(DESTDIR)$(PNGDIR)/16x16/apps/nsnake.png $(MUTE)install -pdm755 $(DESTDIR)$(PNGDIR)/32x32/apps/ $(MUTE)install -pm644 misc/nsnake32.png $(DESTDIR)$(PNGDIR)/32x32/apps/nsnake.png $(MUTE)install -pdm755 $(DESTDIR)$(XPMDIR) $(MUTE)install -pm644 misc/nsnake32.xpm $(DESTDIR)$(XPMDIR)/nsnake.xpm $(MUTE)install -pdm755 $(DESTDIR)$(DESKTOPDIR) $(MUTE)install -pm644 misc/nsnake.desktop $(DESTDIR)$(DESKTOPDIR) # $(PACKAGE) successfuly installed! uninstall: # Uninstalling... $(MUTE)rm -f $(DESTDIR)$(BINDIR)/$(EXE) $(MUTE)rm -f $(DESTDIR)$(MANDIR)/$(MANFILE) $(MUTE)rm -f $(DESTDIR)$(PNGDIR)/16x16/apps/nsnake.png $(MUTE)rm -f $(DESTDIR)$(PNGDIR)/32x32/apps/nsnake.png $(MUTE)rm -f $(DESTDIR)$(XPMDIR)/nsnake.xpm $(MUTE)rm -f $(DESTDIR)$(DESKTOPDIR)/nsnake.desktop $(EXE): $(OBJECTS) $(COMMANDER_OBJS) # Linking... $(MUTE)$(CXX) $(OBJECTS) $(COMMANDER_OBJS) -o bin/$(EXE) $(LIBSDIR) $(LDFLAGS) src/%.o: src/%.cpp # Compiling $<... $(MUTE)$(CXX) $(CXXFLAGS) $(CDEBUG) $< -c -o $@ $(DEFINES) $(INCLUDESDIR) dist: clean $(DISTDIR).tar.gz # This creates a tarball with all the files # versioned by GIT. $(DISTDIR).tar.gz: $(DISTDIR) $(MUTE)tar czf $(DISTDIR).tar.gz $(DISTDIR) $(MUTE)rm -rf $(DISTDIR) $(MUTE)cp $(DISTDIR).tar.gz .. $(MUTE)rm -f $(DISTDIR).tar.gz # Created ../$(DISTDIR).tar.gz! # This copies all the source code files into a # subdirectory called $(DISTDIR). # # It uses `git ls-files` to create the directory # tree and copy everything to their respective # places. # $(DISTDIR): # Compressing source code... $(MUTE)mkdir -p $(DISTDIR) -$(MUTE)git ls-files | xargs -L 1 dirname | sed -e 's|^|$(DISTDIR)/|' | xargs -L 1 mkdir -p -$(MUTE)git ls-files | sed -e 's|\(.*\)|\0 $(DISTDIR)/\0|' | xargs -L 1 cp -$(MUTE)rm -f $(DISTDIR)/.gitignore run: all # Running... $(MUTE)./bin/$(EXE) clean: # Cleaning files... $(MUTE)rm $(VTAG) -f $(OBJECTS) $(COMMANDER_OBJS) $(MUTE)rm $(VTAG) -f bin/$(EXE) dirs: $(MUTE)mkdir -p bin doc: # Generating documentation... $(MUTE)doxygen Doxyfile docclean: # Removing documentation... -$(MUTE)rm $(VTAG) -rf doc/html .PHONY: clean dirs doc docclean uninstall # commander stuff $(COMMANDERDIR)/commander.o: $(COMMANDERDIR)/commander.c # Compiling $<... $(MUTE)$(CC) $(COMMANDER_CFLAGS) $< -c -o $@ nSnake-3.0.1/NEWS000066400000000000000000000002751236634675000134510ustar00rootroot00000000000000For up-to-date nsnake news, go to: http://alexdantas.net/stuff/posts/category/projects/nsnake/ Also, here's a RSS feed: http://alexdantas.net/stuff/posts/category/projects/nsnake/feed/ nSnake-3.0.1/README.md000066400000000000000000000150521236634675000142300ustar00rootroot00000000000000# nsnake Classic snake game on the terminal; made with C++ and ncurses. ![menu](http://nsnake.alexdantas.net/images/arcade-menu.png) ![menu](http://nsnake.alexdantas.net/images/small-maze.png) ![menu](http://nsnake.alexdantas.net/images/large-maze-with-lots-of-fruits.png) `nsnake` is a clone of the classic snake game that we all used to play on our cellphones. You play this game on the terminal, with textual interface. Here's some features: * Customizable gameplay, appearance and key bindings * GUI-like interface with nice animations * Levels - create your own [easily][level_wiki]; * Lots of possible game modes; teleport, scroll map, random walls, etc. Scores are saved for each setting. ## Controls The keybindings are completely customizable on the in-game menus. The default ones are: | Keys | Actions | | ----------------- | -------------------------- | | Arrow Keys | Moves the snake | | q | Quits the game at any time | | p | Pauses/Unpauses the game | | h | Show help during game | All user settings are located at `~/.local/share/nsnake`. ## Levels nSnake looks out for level files on `~/.local/share/nsnake/levels`. By default it installs stock levels on `/usr/share/games/nsnake/levels`, so make sure to copy them before playing. Levels have a `.nsnake` extension and are simple text files. The file format is very easy to edit; check out [the wiki][level_wiki] for a quick guide. ## Dependencies nSnake only depends on `ncurses`; it's used to show cute things on the terminal. Make sure you have the package *ncurses dev*. Note that it's _not_ the default that comes with your distro. | Distro | Installation command | | -------------- | --------------------------------- | | Ubuntu/Debian | `apt-get install libncurses5-dev` | | Fedora | `yum install ncurses-devel` | | Arch Linux | _comes by default_ | If you know the means to install on other distros, [please tell me][issues]. ## Usage Briefly, the following shell commands should configure, build and install this package on the default directories: $ make $ [sudo] make install If you want to test it before installing, do: $ make run Then, it's simple: $ man nsnake $ nsnake To change the default installation directories, check file `INSTALL.md`. ## Contact Hello there, I'm Alexandre Dantas (kure)! Thanks for downloading and playing this game, I'm very glad you had interest on this humble project. You can send me comments, bugs, ideas or anything else by email. And if you have time, please visit my blog! * My email: * My homepage: http://alexdantas.net/ * Mu blog: http://alexdantas.net/stuff I'd appreciate any commentary - even if it's just _"Hello, I play your game!"_. ## Links Some interesting links if you're interested on nsnake: * [nSnake homepage][home] * [Up-to-date nsnake news][news] ([RSS feed][rss]) Also, it's all over the web! * [Source on GitHub][github] * [Source on Bitbucket][bitbucket] * [Source on Sourceforge][sourceforge] * [Source on Google Code][googlecode] * [Source on Gitcafe][gitcafe] * [Package on Debian](https://tracker.debian.org/pkg/nsnake) * [Package on Fedora](https://apps.fedoraproject.org/packages/nsnake) * [Package on Arch Linux (AUR)](https://aur.archlinux.org/packages/nsnake/) * [Package on Slackware (SlackBuilds)](http://slackbuilds.org/repository/14.1/games/nSnake/) ## Credits Firstly, I'd like to thank you for playing this game. Hope you liked it! The game was mainly done by me, but a lot of people helped me in many ways. There's a list of them on the `AUTHORS` file, and you can see people's [contributions to the code here][contrib]. Throughout the nsnake development, I found very interesting sources of ideas; small games made in C, textual games with ncurses, and stuff like that. I raise my hat to: * *nInvaders*: A space invaders-like game using ncurses. * homepage: http://ninvaders.sourceforge.net * comments: "Thanks for the inspiration. If I haven't installed this in first place, I'd never have the idea for nsnake" * *pacman4console*: A console-based pacman game. * homepage: http://doctormike.googlepages.com/pacman.html * comments: "The way this package was organized is incredible. Thanks for the general packaging ideas" * *vadorz*: An addicting ncurses space-invaders game. * homepage: http://code.google.com/p/vadorz/ * comments: "This package is awesome, 'cause it uses ncurses AND pdcurses to be able to play in Windows and GNU/Linux" * *snake4*: Fruit-eating snake game * homepage: http://shh.thathost.com/ * comments: "Thanks for the Makefile ideas. It's very well-written." * *ASCII Generator*: A generator of awesome ASCII text arts * homepage: http://www.network-science.de/ascii/ * *Text ASCII Art Generator*: Another awesome ASCII text generator * homepage: http://patorjk.com/software/taag/ * comments: "I've used the Modular font to display the Game Over screen" ## License nsnake - The classic snake game with ncurses. Copyright (C) 2011,2014 Alexandre Dantas nsnake 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 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 . homepage: http://www.alexdantas.net/projects/nsnake/ mailto: eu@alexdantas.net [issues]: https://github.com/alexdantas/nSnake/issues [home]: http://nsnake.alexdantas.net/ [news]: http://alexdantas.net/stuff/posts/category/projects/nsnake/ [rss]: http://alexdantas.net/stuff/posts/category/projects/nsnake/feed/ [github]: https://github.com/alexdantas/nsnake [bitbucket]: https://bitbucket.org/alexdantas/nsnake [sourceforge]: https://sourceforge.net/projects/nsnake/ [gitcafe]: https://gitcafe.com/alexdantas/nsnake [googlecode]: http://code.google.com/p/nsnake/ [contrib]: https://github.com/alexdantas/nSnake/graphs/contributors [level_wiki]: https://github.com/alexdantas/nSnake/wiki/How-to-create-your-own-levels nSnake-3.0.1/TODO000066400000000000000000000061301236634675000134360ustar00rootroot00000000000000This is a long-term list of things that might be nice to have on the game. If you want to add a suggestion, by all means create an issue here: https://github.com/alexdantas/nsnake/issues?state=open -------------------------------------------------------------------------------- [easy] When the snake eats a frut, modify its body so that the place where the fruit was becomes big.. Just like when a snake eats it gets stuffed [medium] Attribute that changes the snake color Like -c [0 ~ 4], changes colors from 0 to 4 Change more colors? Key 'c' changes the color in-game [simple] Make the game speed changing more balanced Make the fruit give score equal to game level [medium] Make attribute that changes the game level size? Follow the other 'nsnake' path and handle the SIGWINCH signal? This way, whenever someone resize the window, i could do whatever i want, like resize the game window or move the screen... [hard] Change level to be a big char grid. Each cell will be able to be EMPTY, FRUIT, WALL or SNAKE. When checking collision, simply watch if snake's head is in a WALL cell Same for when snake's head is in a FRUIT cell And same when creating a fruit! Instead of checking each and every snake position, check only if the grid is a SNAKE. (the only exception would be the snake head. how can i check collision with it if it's a SNAKE cell?) This will make possible to create LEVELS for the game! Just read a text file with real grids on it, parse and voila! I wonder if this would be the game be complicated... Need to check other game's examples. [simple] Insert header when writing to score file so i can check if it is corrupted. Whenever i read from the score file, check if the header is ok. Maybe some special number like 666 or a string like "nSnAkE_sCoReFiLe" What will i do if i find a corrupted score file? Erase the score? Rewrite it? I need to warn the player, right? [hard as hell] Change the way highscores exist. Maybe implement a highscore screen, with the names of the players. Assume default name as getenv("USER") and such. [simple] Leave nsnake with the default background color of the user terminal, instead of COLOR_BLACK. Since use_default_colors is a ncurses extension, the man page recommends conditioning it to NCURSES_VERSION. What does that mean? [simple] Deal with signals, in case someone does Ctrl+C. Exit from ncurses and free() the snake memory :D [simple] Create function that will verify if the user pressed something (kbhit()) If the user did, go to the nonblocking input functions (saves memory) [simple] When clearing highscores, show if it worked or not (due to several reasons) Show the hscore directory? ------------------------------------------- * Campaign Mode? Change levels? > way ahead *** Increase or decrease movement speed according to the game * Possibility to change the controls (add config option - add controls.bin file) **** Include libncurses with the source? ***** True Highscore Support: For each mode, its own Highscore *** Windows support: PDCurses! ------------------------------------------ IMPLEMENT IT ON C++ nSnake-3.0.1/deps/000077500000000000000000000000001236634675000137015ustar00rootroot00000000000000nSnake-3.0.1/deps/commander/000077500000000000000000000000001236634675000156465ustar00rootroot00000000000000nSnake-3.0.1/deps/commander/commander.c000066400000000000000000000115621236634675000177640ustar00rootroot00000000000000/* commander.c - Commander option parser ported to C. * http://github.com/clibs/commander * * Copyright (c) 2012 TJ Holowaychuk * * Slightly modified so it could fit nFlood. */ #include #include #include #include #include "commander.h" /** Output error and exit. */ static void error(char *msg) { fprintf(stderr, "%s\n", msg); exit(1); } void command_init(command_t *self, const char *name, const char *version) { self->arg = NULL; self->name = name; self->version = version; self->option_count = self->argc = 0; self->usage = "[options]"; self->nargv = NULL; } void command_free(command_t *self) { int i; for (i = 0; i < self->option_count; ++i) { command_option_t *option = &self->options[i]; free(option->argname); free(option->large); } if (self->nargv) { for (i = 0; self->nargv[i]; ++i) { free(self->nargv[i]); } free(self->nargv); } } /** Parse argname from `str`. * For example: * Take "--required " and populate `flag` * with "--required" and `arg` with "". */ static void parse_argname(const char *str, char *flag, char *arg) { int buffer = 0; size_t flagpos = 0; size_t argpos = 0; size_t len = strlen(str); size_t i; for (i = 0; i < len; ++i) { if (buffer || '[' == str[i] || '<' == str[i]) { buffer = 1; arg[argpos++] = str[i]; } else { if (' ' == str[i]) continue; flag[flagpos++] = str[i]; } } arg[argpos] = '\0'; flag[flagpos] = '\0'; } /** Normalize the argument vector by exploding * multiple options (if any). * For example * "foo -abc --scm git" -> "foo -a -b -c --scm git" */ static char ** normalize_args(int *argc, char **argv) { int size = 0; int alloc = *argc + 1; char **nargv = malloc(alloc * sizeof(char *)); int i; for (i = 0; argv[i]; ++i) { const char *arg = argv[i]; size_t len = strlen(arg); // short flag if (len > 2 && '-' == arg[0] && !strchr(arg + 1, '-')) { alloc += len - 2; nargv = realloc(nargv, alloc * sizeof(char *)); size_t j; for (j = 1; j < len; ++j) { nargv[size] = malloc(3); sprintf(nargv[size], "-%c", arg[j]); size++; } continue; } // regular arg nargv[size] = malloc(len + 1); strcpy(nargv[size], arg); size++; } nargv[size] = NULL; *argc = size; return nargv; } void command_option(command_t *self, const char *small, const char *large, const char *desc, command_callback_t cb) { if (self->option_count == COMMANDER_MAX_OPTIONS) { command_free(self); error("Maximum option definitions exceeded"); } int n = self->option_count++; command_option_t *option = &self->options[n]; option->cb = cb; option->small = small; option->description = desc; option->required_arg = option->optional_arg = 0; option->large_with_arg = large; option->argname = malloc(strlen(large) + 1); assert(option->argname); option->large = malloc(strlen(large) + 1); assert(option->large); parse_argname(large, option->large, option->argname); if ('[' == option->argname[0]) option->optional_arg = 1; if ('<' == option->argname[0]) option->required_arg = 1; } /** Parse `argv` (internal). * Input arguments should be normalized first * see `normalize_args`. */ static void command_parse_args(command_t *self, int argc, char **argv) { int literal = 0; int i, j; for (i = 1; i < argc; ++i) { const char *arg = argv[i]; for (j = 0; j < self->option_count; ++j) { command_option_t *option = &self->options[j]; // match flag if (!strcmp(arg, option->small) || !strcmp(arg, option->large)) { self->arg = NULL; // required if (option->required_arg) { arg = argv[++i]; if (!arg || '-' == arg[0]) { fprintf(stderr, "%s %s argument required\n", option->large, option->argname); command_free(self); exit(1); } self->arg = arg; } // optional if (option->optional_arg) { if (argv[i + 1] && '-' != argv[i + 1][0]) { self->arg = argv[++i]; } } // invoke callback option->cb(self); goto match; } } // -- if ('-' == arg[0] && '-' == arg[1] && 0 == arg[2]) { literal = 1; goto match; } // unrecognized if ('-' == arg[0] && !literal) { fprintf(stderr, "unrecognized flag %s\n", arg); command_free(self); exit(1); } int n = self->argc++; if (n == COMMANDER_MAX_ARGS) { command_free(self); error("Maximum number of arguments exceeded"); } self->argv[n] = (char *) arg; match:; } } void command_parse(command_t *self, int argc, char **argv) { self->nargv = normalize_args(&argc, argv); command_parse_args(self, argc, self->nargv); self->argv[self->argc] = NULL; } nSnake-3.0.1/deps/commander/commander.h000066400000000000000000000033161236634675000177670ustar00rootroot00000000000000/* commander.c - Commander option parser ported to C. * http://github.com/clibs/commander * * Copyright (c) 2012 TJ Holowaychuk * * Slightly modified so it could fit yetris. */ /** Safeguard for C++ name-mangling */ #ifdef __cplusplus extern "C" { #endif #ifndef COMMANDER_H #define COMMANDER_H /* * Max options that can be defined. */ #ifndef COMMANDER_MAX_OPTIONS #define COMMANDER_MAX_OPTIONS 32 #endif /* * Max arguments that can be passed. */ #ifndef COMMANDER_MAX_ARGS #define COMMANDER_MAX_ARGS 32 #endif /* * Command struct. */ struct command; /* * Option callback. */ typedef void (* command_callback_t)(struct command *self); /* * Command option. */ typedef struct { int optional_arg; int required_arg; char *argname; char *large; const char *small; const char *large_with_arg; const char *description; command_callback_t cb; } command_option_t; /* * Command. */ typedef struct command { void *data; const char *usage; const char *arg; const char *name; const char *version; int option_count; command_option_t options[COMMANDER_MAX_OPTIONS]; int argc; char *argv[COMMANDER_MAX_ARGS]; char **nargv; } command_t; /** Initialize with program `name` and `version`. */ void command_init(command_t *self, const char *name, const char *version); /** Free up commander after use. */ void command_free(command_t *self); /** Define an option. */ void command_option(command_t *self, const char *small, const char *large, const char *desc, command_callback_t cb); /** Parse `argv` (public). */ void command_parse(command_t *self, int argc, char **argv); #endif /* COMMANDER_H */ /** Safeguard for C++ name-mangling */ #ifdef __cplusplus } #endif nSnake-3.0.1/deps/commander/package.json000066400000000000000000000004121236634675000201310ustar00rootroot00000000000000{ "name": "commander", "version": "1.3.2", "repo": "clibs/commander", "description": "Command-line argument parser", "keywords": ["cli", "command", "parser", "argv", "args", "options"], "license": "MIT", "src": ["src/commander.h", "src/commander.c"] } nSnake-3.0.1/doc/000077500000000000000000000000001236634675000135135ustar00rootroot00000000000000nSnake-3.0.1/doc/doxygen_mainpage.hpp000066400000000000000000000011021236634675000175340ustar00rootroot00000000000000/// @mainpage Documentation Main Page /// /// _Welcome to the nSnake code documentation!_

/// /// This page is for people who want to understand how nSnake was made /// (it's source code).
/// If you're not after this, check out the links below: /// /// * [nSnake homepage](http://nsnake.alexdantas.net) /// * [nSnake on GitHub](https://github.com/alexdantas/nsnake) /// /// \tableofcontents /// /// ## Introduction /// /// ...I really should put something here... /// /// ## Contact /// /// Any questions, please email me at nSnake-3.0.1/doc/man/000077500000000000000000000000001236634675000142665ustar00rootroot00000000000000nSnake-3.0.1/doc/man/nsnake.6000066400000000000000000000045571236634675000156470ustar00rootroot00000000000000\" ----------------------------- nsnake man page ------------------------------- .TH nsnake 6 "DATE" vVERSION "nsnake vVERSION" \"---------------------------------- NAME -------------------------------------- .SH NAME nsnake \- Classic snake game on the terminal \"-------------------------------- SYNOPSIS ------------------------------------ .SH SYNOPSIS nsnake .RB [ -h ] .RB [ -v ] \"------------------------------- DESCRIPTION ---------------------------------- .SH DESCRIPTION nsnake is the classic snake game with textual interface. You can play it at command-line and uses the nCurses library for graphics. The rules are the same of any snake game: .PP You control a hungry snake and the objective is to eat as many fruits you can. Each fruit eaten increases your size. The game ends when the snake collides with the walls or itself. .PP The challenge is to earn the biggest score possible by eating as many fruits as you can. .PP Controls and game settings can be changed through the in-game menus or by directly editing the settings file. .B Default controls: .RS .BR "Arrow Keys " "Moves the snake" .BR "q " "Quits the game at any time" .BR "p " "Pauses/Unpauses the game" .BR "h " "Show help during the game" .RE \"--------------------------------- OPTIONS ------------------------------------ .SH OPTIONS .TP .B "-h, --help" Displays the quick help text. .TP .B "-v, --version" Displays the version and general information. \"----------------------------------- FILES ------------------------------------- .SH FILES .BR "$(HOME)/.local/share/nsnake/" " Per-user content; game settings and scores" \"----------------------------------- SEE ALSO ---------------------------------- .SH SEE ALSO .BR "snake4" "(6) " "ktron" "(6) " "yetris" "(6) " \"----------------------------------- BUGS ------------------------------------- .SH BUGS Probably. If you find any, please report it at the issue tracker (https://github.com/alexdantas/nsnake/issue) or email me at . \"---------------------------------- AUTHOR ------------------------------------ .SH AUTHOR .PP This manual page and nsnake were both written by Alexandre Dantas . \"----------------------------------- WWW ------------------------------------- .SH WWW * http://nsnake.alexdantas.net/ * https://github.com/alexdantas/nsnake/ nSnake-3.0.1/levels/000077500000000000000000000000001236634675000142405ustar00rootroot00000000000000nSnake-3.0.1/levels/3-hearts.nsnake000066400000000000000000000033241236634675000170710ustar00rootroot00000000000000name=3 Hearts author=Alexandre Dantas date=Jul 23, 2014 comment=Cute, right? start ############################################################################## # # # #### #### # # ################# # # ################### # # ################### # # ################# # # ############## # # ########### #### #### # # ##### ################# # # # ################### # # ################### ##### #### # # ################ ################ # # ############# ################## # # ######### ################## # # ##### ################ # # # ############ # # @ ######## # # ## # # # ############################################################################## end nSnake-3.0.1/levels/NSNAKE.nsnake000066400000000000000000000034701236634675000164240ustar00rootroot00000000000000# Yay, this is a comment! ; And so is this! name = NSNAKE author = Alexandre Dantas date = Jul 23, 2014 comment = How meta. Watch out for the letter K! start ############################################################################# # # # # # # # # # # ###### # # # ## # # ## # ####### # # ###### # # ### # # ### # ####### # # # # # # # # ###### # # # ####### #### # # # # # # # # # # # # #### ###### # # # ### # # ### # # # # # # # # ## ###### # ## # # # # # # # # # ###### # # # # # # @ # # # # # # # # # ############################################################################# end ; You can add secret stuff here too! nSnake-3.0.1/levels/arena00.nsnake000066400000000000000000000050571236634675000166760ustar00rootroot00000000000000; This is a level file for the nSnake game ; It has a very simple format: ; ; - All lines starting with ';' are comments ; and will be ignored ; - Blank lines are also ignored ; - You can provide some level metadata ; before the level. Just make sure to ; - Put the field name without spaces right ; next to '=', for example "name=" or "author=". ; - The metadata's content goes right after '=' ; until the end of line. ; - All the level's contents must be between ; lines with only "start" and "end". ; ; About the level itself: ; ; - Walls are marked with '#' ; - Player start position is marked with '@'. ; Note that the player will always start to the right ; - Everything else is treated as empty space. ; I highly recommend using ' ' for this. ; ; Any questions, message me at . Enjoy! name=Arena 00 author=Alexandre Dantas date=Jul 22, 2014 comment=The very first level made for nSnake! start ############################################################################## # # # # # # # # # ######################## ###################### # # # # # # # # # # # # # # # # # # @ ##### # # # # # # # # # # # # # # # # # # ######################## ###################### # # # # # # # # # ############################################################################## end nSnake-3.0.1/levels/arena01.nsnake000066400000000000000000000033551236634675000166760ustar00rootroot00000000000000name=Arena 01 author=Alexandre Dantas date=Jul 23, 2014 comment=Random level that came across my mind start ############################################################################## # # # # # # # # # # # # # # # # # # # # # # # ####################### @ ##################### # # # # # # # # # # # # # # # # # # # # # # # ############################################################################## end nSnake-3.0.1/levels/arena02.nsnake000066400000000000000000000033531236634675000166750ustar00rootroot00000000000000name = Arena 02 author = Alexandre Dantas date = Jul 28, 2014 comment = Nice box in the middle start ############################################################################## # # # # # # # ################ # # # # # # # # # # # # # # # # # # # # @ # # # # # # # # # # # # # # # # # # # # # # ################ # # # # # # # ############################################################################## endnSnake-3.0.1/levels/balls.nsnake000066400000000000000000000033551236634675000165440ustar00rootroot00000000000000name = Balls author = Alexandre Dantas date = Jul 28, 2014 comment = Random balls on the field start ############################################################################## # ######## ######## ######## ######## # # ######## ######## ######## ######## # # ###### ###### ###### ###### # # #### #### #### #### # # # # # # #### #### #### # # ###### ###### ###### # # ######## ######## ######## # # ######## ######## ######## # # ######## ######## ######## # # ###### ###### ###### # # #### #### #### # # # # @ # # # # #### #### #### #### # # ###### ###### ###### ###### # # ######## ######## ######## ######## # ############################################################################## end nSnake-3.0.1/levels/cave00.nsnake000066400000000000000000000033411236634675000165200ustar00rootroot00000000000000name = Cave 00 author = Alexandre Dantas date = Jul 27, 2014 comment = Strange level start ############################################################################## # ######################################################################## # # ########### ################################################ # # ######### ######### ############## ############### # # ####### ##### ########## ########## # # ### ######### ###### # # ########## # # ########## # # ######### # # @ ###### # # # # # # ####### # # ########### # # ############# # ################## ##### # ################# ###### ####### ## ################# ######## ######### ##### ################# ################### ########## ########## ################### ################################################## ############################################################################## endnSnake-3.0.1/levels/diagonal-split.nsnake000066400000000000000000000033211236634675000203470ustar00rootroot00000000000000name = Diagonal Split author = Alexandre Dantas date = Jul 27, 2014 start ############################################################################## # # # ##### # # #### # # #### # # ##### # # #### # # #### # # @ ##### # # #### # # #### # # #### # # ##### # # #### # # #### # # ##### # # #### # # #### # # ##### # # # ############################################################################## endnSnake-3.0.1/levels/horizontal-madness.nsnake000066400000000000000000000034021236634675000212610ustar00rootroot00000000000000name = Horizontal Madness author = Alexandre Dantas date = Jul 28, 2014 comment = Cheap rip-off of "Vertical Madness" start ############################################################################## # # # # # # #################################################################### # # # # # # # # #################################################################### # # # @ # # # #################################################################### # # # # # # # # #################################################################### # # # # # # ############################################################################## endnSnake-3.0.1/levels/lenny.nsnake000066400000000000000000000033501236634675000165670ustar00rootroot00000000000000name=Le Lenny Faec author=Alexandre Dantas date=Jul 23, 2014 comment=http://alexdantas.net/lenny start ############################################################################## # # # @ # # ### ### # # ### ### # # ### ########### ######### ### # # #### ####### ## # ####### ## #### # # #### ####### ## # ###### ##### # # ##### ##### # #### ##### # # #### ## #### # # #### #### #### # # #### ## #### # # ##### ## #### # # ##### ## ##### # # ##### ######### #### # # #### ##### #### # # #### ### ## #### # # ### ### ### ### # # ############ # # # ############################################################################## end nSnake-3.0.1/levels/retro00.nsnake000066400000000000000000000006041236634675000167340ustar00rootroot00000000000000name = Retro 00 author = Alexandre Dantas date = Jul 28, 2014 comment = Taken straight from the old game "Snake II" start ###################### # # # # # # # # # @ # # # # # # # # # ###################### end nSnake-3.0.1/levels/retro01.nsnake000066400000000000000000000006211236634675000167340ustar00rootroot00000000000000name = Retro 01 author = Alexandre Dantas date = Jul 28, 2014 comment = Another oldie from the classic "Snake II" cellphone game start ###################### ### ### ## ## # # # ##### # # @ # # ##### # # # ## ## ### ### ###################### end nSnake-3.0.1/levels/retro02.nsnake000066400000000000000000000006211236634675000167350ustar00rootroot00000000000000name = Retro 02 author = Alexandre Dantas date = Jul 28, 2014 comment = Another oldie from the classic "Snake II" cellphone game start ###################### # # # # # # # # ########## # # # # # # # # @ # # ########### # # # # # # # # ###################### end nSnake-3.0.1/levels/retro03.nsnake000066400000000000000000000006211236634675000167360ustar00rootroot00000000000000name = Retro 03 author = Alexandre Dantas date = Jul 28, 2014 comment = Another oldie from the classic "Snake II" cellphone game start ###################### ###################### ## ## ## # # ## ## # # ## # # # # ## # # ## ## # # ## ## @ ## ###################### ###################### end nSnake-3.0.1/levels/retro04.nsnake000066400000000000000000000006211236634675000167370ustar00rootroot00000000000000name = Retro 04 author = Alexandre Dantas date = Jul 28, 2014 comment = Another oldie from the classic "Snake II" cellphone game start ###################### #### ############ # ## # # # # # ########### ######### # @ # # # ###################### # # # # # # ###################### end nSnake-3.0.1/levels/retro05-2.nsnake000066400000000000000000000005701236634675000171020ustar00rootroot00000000000000name = Retro 05 v2 author = Alexandre Dantas date = Jul 28, 2014 comment = An alternative to "Retro 05" start ###################### # # # # # # # # ##### ##### # # # @ # # # ##### ##### # # # # # # # # ###################### end nSnake-3.0.1/levels/retro05.nsnake000066400000000000000000000006161236634675000167440ustar00rootroot00000000000000name = Retro 05 author = Alexandre Dantas date = Jul 28, 2014 comment = You'd probably want to play this on the teleport mode start ###################### # # # # # # # # ##### ###### ##### # # # # # @ # # # # ##### # # ##### # # # # # # # # ###################### end nSnake-3.0.1/levels/retro06.nsnake000066400000000000000000000005721236634675000167460ustar00rootroot00000000000000name = Retro 06 author = Alexandre Dantas date = Jul 28, 2014 comment = Also suited for the teleport mode start ###################### # # # # # # # # # # # # ###################### # # # # # @ # # # # # # # ###################### end nSnake-3.0.1/levels/retro07.nsnake000066400000000000000000000005721236634675000167470ustar00rootroot00000000000000name = Retro 07 author = Alexandre Dantas date = Jul 28, 2014 comment = Also suited for the teleport mode start ###################### # # # # # @ # # # ###################### # # # # # # # # # # # # # # # # ###################### end nSnake-3.0.1/levels/s.nsnake000066400000000000000000000033231236634675000157040ustar00rootroot00000000000000name=S author=Alexandre Dantas date=Jul 23, 2014 comment=Yep, the letter S start ############################################################################## # # # # # # # # # # # ################################################################ # ################################################################ # # # # # @ # # # # # ########################################################## # ########################################################## # # # # # # # # # # # ############################################################################## end nSnake-3.0.1/levels/spiral-large.nsnake000066400000000000000000000033661236634675000200330ustar00rootroot00000000000000name = Spiral (Large) author = Alexandre Dantas date = Jul 28, 2014 comment = That's really tough, right? start ############################################################################## # # # # # ########################################################################## # # # # # # # # # # # #################################################################### # # # # # # # # # # # # # # # # # ############################################################## # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ################################################################# # # # # # # # # # # # # # # ####################################################################### # # # @ # # # ############################################################################## endnSnake-3.0.1/levels/squares00.nsnake000066400000000000000000000033011236634675000172610ustar00rootroot00000000000000name=Squares 01 author=Alexandre Dantas date=Jul 23, 2014 start ############################################################################## # ################### # # @ ################### # # ################### # # ################### # # ################### # ################# ################### ################ ################# ################### ################ ################# ################ ################# ################ ################# ################ ################# ################ ################# ################ ################# ################ ################# ################### ################ # ################### # # ################### # # ################### # # ################### # # ################### # ############################################################################## end nSnake-3.0.1/levels/squares01.nsnake000066400000000000000000000033011236634675000172620ustar00rootroot00000000000000name=Squares 01 author=Alexandre Dantas date=Jul 23, 2014 start ############################################################################## ############## ############### ############## ############### ############## ############### ############## ############### ############## ############### ############## ############### ############## ################ ############### # ################ # # ################ # #@ ################ # # ################ # # ################ # ############## ################ ############### ############## ############### ############## ############### ############## ############### ############## ############### ############## ############### ############## ############### ############################################################################## end nSnake-3.0.1/levels/squares02.nsnake000066400000000000000000000033011236634675000172630ustar00rootroot00000000000000name=Squares 02 author=Alexandre Dantas date=Jul 23, 2014 start ############################################################################## # # # ############# # # ############# # # ############# # # ############# # # ############# # # ################# ################ # # ################# ################ # # ################# ################ # # ################# ################ # # ################# ################ # # ################# ################ # # ################# ################ # # ############# # # ############# # # ############# # # ############# # # @ ############# # # # ############################################################################## end nSnake-3.0.1/levels/tetris.nsnake000066400000000000000000000033421236634675000167550ustar00rootroot00000000000000name = Tetris author = Alexandre Dantas date = Jul 27, 2014 comment = Try dodgin' em! start ############################################################################## # # # ##### #################### ##### # # ##### #################### ##### ########## # # ##### #################### ##### ########## # # ##### #################### ##### ########## # # ########## ########## ########## # # ########## ########## ########## ##### # # ########## ########## ########## ##### # # ########## ########## ########## ##### # # ##### ########## ##### ##### # # ##### ##### ########## ##### ########## ##### # # ##### ##### ########## ##### ########## ##### # # ##### ##### ########## ##### ########## ##### # # ##### ########## ########## ##### # # ##### ########## ########## # # ##### ########## ########## # # ##### ########## ########## # #@ ##### ########## ########## # # # ############################################################################## endnSnake-3.0.1/levels/tricky-horizontal.nsnake000066400000000000000000000034271236634675000211430ustar00rootroot00000000000000name = Tricky (Horizontal) author = Alexandre Dantas date = Jul 28, 2014 comment = Let's see you survive on this baby. While it scrolls. start ############################################################################## # # # @ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ############################################################################## end nSnake-3.0.1/levels/tricky-vertical.nsnake000066400000000000000000000034251236634675000205610ustar00rootroot00000000000000name = Tricky (Vertical) author = Alexandre Dantas date = Jul 28, 2014 comment = Let's see you survive on this baby. While it scrolls. start ############################################################################## # # # # # # # # # # # # # # #@ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ############################################################################## end nSnake-3.0.1/levels/tricky.nsnake000066400000000000000000000034121236634675000167460ustar00rootroot00000000000000name = Tricky author = Alexandre Dantas date = Jul 28, 2014 comment = Let's see you survive on this baby. While it scrolls. start ############################################################################## ## # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # @ # ## # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ############################################################################## end nSnake-3.0.1/levels/vertical-madness.nsnake000066400000000000000000000033721236634675000207070ustar00rootroot00000000000000name = Vertical Madness author = Alexandre Dantas date = Jul 27, 2014 comment = Kinda boring without teleport start ############################################################################## # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # @ # # # # # # # # # # # # # ############################################################################## endnSnake-3.0.1/levels/w.nsnake000066400000000000000000000033611236634675000157120ustar00rootroot00000000000000 name = W author = Alexandre Dantas date = Jul 27, 2014 comment = Nothing fancy, just the letter W start ############################################################################## # ##### ##### # # ##### ##### # # ##### ##### # # ##### ##### # # ##### ##### # # ##### ##### # # ##### ##### # # ##### ##### # # ##### ##### # # ##### ##### ##### # # ##### # # ##### # # ##### # # ##### # # ##### # # ##### # # ##### # # @ ##### # # ##### # ############################################################################## end nSnake-3.0.1/levels/z.nsnake000066400000000000000000000033231236634675000157130ustar00rootroot00000000000000name=Z author=Alexandre Dantas date=Jul 23, 2014 comment=Mirrored letter S start ############################################################################## # # # # # # # # # # ########################################################## # ########################################################## # # # # # # @ # # # # # # ################################################################ # ################################################################ # # # # # # # # # # ############################################################################## end nSnake-3.0.1/misc/000077500000000000000000000000001236634675000137015ustar00rootroot00000000000000nSnake-3.0.1/misc/nsnake.desktop000066400000000000000000000005501236634675000165530ustar00rootroot00000000000000[Desktop Entry] Type=Application Version=1.0 Name=nsnake GenericName=Classic snake game on the terminal GenericName[pt_BR]=Bom e velho jogo da cobra no terminal GenericName[fi]=Matopeli terminaalissa GenericName[it_IT]=Classico gioco snake da terminale Exec=nsnake Terminal=true Icon=nsnake Categories=Game;ActionGame; Keywords=snake;ncurses;textual;terminal; nSnake-3.0.1/misc/nsnake16.png000066400000000000000000000010631236634675000160350ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  tIME +_IDAT8ұjSqMdHH%rmt(;8)m Zq*֩P0/  E(o.BƈXhqh܊8_p~?΁!Fd\3<^% W݇Xȿ !>5G-6UW^'(~IlP(ZVR~/uh4T,d2(_?u M58IENDB`nSnake-3.0.1/misc/nsnake32.xpm000066400000000000000000000077101236634675000160600ustar00rootroot00000000000000/* XPM */ static char * nsnake32_xpm[] = { "32 32 113 2", " c None", ". c #000000", "+ c #FF0000", "@ c #003900", "# c #00C900", "$ c #00F400", "% c #00B800", "& c #001100", "* c #470000", "= c #DE0000", "- c #D40000", "; c #530000", "> c #001C00", ", c #00E700", "' c #004C00", ") c #000C00", "! c #009E00", "~ c #009500", "{ c #001600", "] c #00B900", "^ c #00F100", "/ c #DF0000", "( c #450000", "_ c #250000", ": c #A60000", "< c #008D00", "[ c #007F00", "} c #002100", "| c #00E100", "1 c #008F00", "2 c #000F00", "3 c #009100", "4 c #009D00", "5 c #F10000", "6 c #0A0000", "7 c #00D300", "8 c #002900", "9 c #005C00", "0 c #00EC00", "a c #00A600", "b c #00FB00", "c c #00E400", "d c #001B00", "e c #00E300", "f c #8A0000", "g c #B00000", "h c #2D0000", "i c #00F000", "j c #000B00", "k c #00E800", "l c #003500", "m c #003600", "n c #00FF00", "o c #00F800", "p c #000400", "q c #000500", "r c #390000", "s c #B90000", "t c #7C0000", "u c #00F700", "v c #000E00", "w c #003300", "x c #001A00", "y c #0C0000", "z c #EF0000", "A c #00D900", "B c #003200", "C c #00A300", "D c #009F00", "E c #009000", "F c #A10000", "G c #210000", "H c #410000", "I c #DB0000", "J c #009400", "K c #001700", "L c #00BB00", "M c #00F200", "N c #5A0000", "O c #DA0000", "P c #400000", "Q c #006A00", "R c #000A00", "S c #002B00", "T c #00F500", "U c #292929", "V c #BDBDBD", "W c #D6D6D6", "X c #101010", "Y c #757575", "Z c #717171", "` c #282828", " . c #C1C1C1", ".. c #FFFFFF", "+. c #575757", "@. c #010101", "#. c #E3E3E3", "$. c #030303", "%. c #A1A1A1", "&. c #454545", "*. c #303030", "=. c #BABABA", "-. c #D9D9D9", ";. c #0C0C0C", ">. c #ADADAD", ",. c #3C3C3C", "'. c #5D5D5D", "). c #8A8A8A", "!. c #DEDEDE", "~. c #020202", "{. c #A9A9A9", "]. c #404040", "^. c #050505", ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ", ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ", ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ", ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ", ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ", ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ", ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ", ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ", ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ", ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ", ". . . + . . . . . . @ # $ % & . . . . . . . . . . . . . . . . . ", ". * = + - ; . . . > , ' ) ! ~ . { ] ^ ] { . . { ] ^ ] { . . { ] ", ". / ( + _ : . . . < [ . . } | . ! 1 2 3 4 . . ! 1 2 3 4 . . ! 1 ", ". 5 6 + . . . . . 7 8 9 0 a b . c d . > e . . c d . > e . . c d ", ". f g + h . . . . i j k l m n . o p . q o . . o p . q o . . o p ", ". . r + s t . . . u v k w l n . c x . > e . . c x . > e . . c x ", ". . . + y z . . . A B 9 0 C n . D < v E ! . . D < v E ! . . D < ", ". F G + H I . . . 3 J . . . . . K L M L K . . K L M L K . . K L ", ". N O + I P . . . x k Q R . . . . . . . . . . . . . . . . . . . ", ". . . + . . . . . . S L T 7 . . . . . . . . . . . . . . . . . . ", ". . . + . . . . . . . . . . . . . . . . . . . . . . . . . . . . ", ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ", ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ", ". U V . W X . . U V . W X . . U V . W X . . U V . W X . . U V . ", ". Y Z ` .. . . Y Z ` .. . . Y Z ` .. . . Y Z ` .. . . Y Z ` ", "............+.............+.............+.............+.........", "@.#.$.%.&.. . @.#.$.%.&.. . @.#.$.%.&.. . @.#.$.%.&.. . @.#.$.%.", "*.=.. -.;.. . *.=.. -.;.. . *.=.. -.;.. . *.=.. -.;.. . *.=.. -.", "..........+.............+.............+.............+...........", ">.,.'.).. . . >.,.'.).. . . >.,.'.).. . . >.,.'.).. . . >.,.'.).", "!.~.{.].. . ^.!.~.{.].. . ^.!.~.{.].. . ^.!.~.{.].. . ^.!.~.{.].", ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "}; nSnake-3.0.1/src/000077500000000000000000000000001236634675000135355ustar00rootroot00000000000000nSnake-3.0.1/src/Config/000077500000000000000000000000001236634675000147425ustar00rootroot00000000000000nSnake-3.0.1/src/Config/Arguments.cpp000066400000000000000000000034631236634675000174210ustar00rootroot00000000000000#include #include // local files #include #include // Local functions that'll be used as callbacks // for the right switches. void version(command_t* self) { (void)(self); std::cout << "nsnake v" VERSION " (" DATE ")\n" "\n" " , ,\n" " / \\ This game was made with\n" " ((__-^^-,-^^-__)) and itself is Free Software,\n" " `-_---' `---_-' licensed under the GPLv3\n" " `--|o` 'o|--' <3\n" " \\ ` /\n" " ): :(\n" " :o_o:\n" " -\n" "\n" "Homepage: http://nsnake.alexdantas.net/\n" "Source Code: https://github.com/alexdantas/nsnake/\n" "Contact: Alexandre Dantas \n"; command_free(self); exit(EXIT_SUCCESS); } void help(command_t* self) { (void)(self); std::cout << " _ __ _ __ _ ____ \n" "| |\\ | ( (` | |\\ | / /\\ | |_/ | |_ \n" "|_| \\| _)_) |_| \\| /_/--\\ |_| \\ |_|__ \n" "v" VERSION " (built " DATE ")\n" "\n" "nsnake is the classical snake game on the terminal\n" "\n" "Settings and scores are stored at:\n" " `~/.local/share/nsnake/`\n" "\n" "Usage:\n" " nsnake [options]\n" "\n" " -h, --help Show this message\n" " -v, --version Show game version and contact info\n" "\n" "See also `man nsnake`\n" "Thanks for playing this game :)\n"; command_free(self); exit(EXIT_SUCCESS); } void Arguments::parse(int argc, char* argv[]) { // commander internal data structure command_t cmd; command_init(&cmd, argv[0], VERSION); command_option(&cmd, "-v", "--version", "Show game version and build date", version); command_option(&cmd, "-h", "--help", "Show instructions", help); command_parse(&cmd, argc, argv); command_free(&cmd); } nSnake-3.0.1/src/Config/Arguments.hpp000066400000000000000000000005441236634675000174230ustar00rootroot00000000000000#ifndef ARGUMENTS_H_DEFINED #define ARGUMENTS_H_DEFINED /// Command-line argument parser. /// /// This is a thin interface between yetris and the /// `commander` CLI argument parser. /// Highly game-specific. /// /// Homepage: https://github.com/clibs/commander namespace Arguments { void parse(int argc, char* argv[]); }; #endif //ARGUMENTS_H_DEFINED nSnake-3.0.1/src/Config/Globals.cpp000066400000000000000000000261371236634675000170420ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include // VERSION is formatted like "0.0.1" - i'm skipping the dots char Globals::version[3] = { VERSION[0], VERSION[2], VERSION[4] }; // __ ___ _ ____ _ __ // / /` / / \ | |\ | | |_ | | / /`_ // \_\_, \_\_/ |_| \| |_| |_| \_\_/ // real initialization at init() std::string Globals::Config::directory = ""; std::string Globals::Config::file = ""; std::string Globals::Config::scoresFile = ""; bool Globals::Screen::center_horizontally = true; bool Globals::Screen::center_vertically = true; bool Globals::Screen::show_borders = true; bool Globals::Screen::fancy_borders = true; bool Globals::Screen::outer_border = true; unsigned int Globals::Game::starting_speed = 1; int Globals::Game::fruits_at_once = 1; bool Globals::Game::random_walls = false; bool Globals::Game::teleport = false; std::string Globals::Game::current_level = ""; Globals::Game::BoardSize Globals::Game::board_size = LARGE; Globals::Game::BoardSize Globals::Game::intToBoardSize(int val) { if (val == 0) return Globals::Game::SMALL; if (val == 1) return Globals::Game::MEDIUM; return Globals::Game::LARGE; } int Globals::Game::boardSizeToInt(Globals::Game::BoardSize size) { if (size == Globals::Game::SMALL) return 0; if (size == Globals::Game::MEDIUM) return 1; return 2; } int Globals::Game::board_scroll_delay = 1000; bool Globals::Game::board_scroll_up = false; bool Globals::Game::board_scroll_down = false; bool Globals::Game::board_scroll_left = false; bool Globals::Game::board_scroll_right = false; ColorPair Globals::Theme::text; ColorPair Globals::Theme::hilite_text; ColorPair Globals::Theme::textbox; bool Globals::Error::has_config_file = true; bool Globals::Error::has_score_file = true; bool Globals::Error::old_version_score_file = false; bool Globals::Error::strange_score_file = false; // _ _ _ _____ // | | | |\ | | | | | // |_| |_| \| |_| |_| void Globals::init() { // Other default variables Globals::Theme::text = 0; Globals::Theme::hilite_text = Colors::pair(COLOR_CYAN, COLOR_DEFAULT); Globals::Theme::textbox = (Globals::Theme::hilite_text | A_REVERSE); // Making sure default config directory exists // By default it's `~/.local/share/nsnake/` Globals::Config::directory = (Utils::File::getHome() + ".local/share/" + PACKAGE + "/"); if (Utils::String::front(Globals::Config::directory) != '/') { // We couldn't get user's home directory, // so let's fallback to `/tmp/.local/share...` Globals::Config::directory = ("/tmp/" + Globals::Config::directory); } Globals::Config::file = (Globals::Config::directory + "settings.ini"); Globals::Config::scoresFile = (Globals::Config::directory + "arcade.nsnakescores"); if (! Utils::File::isDirectory(Globals::Config::directory)) Utils::File::mkdir_p(Globals::Config::directory); if (! Utils::File::isDirectory(Globals::Config::directory)) { // We REALLY can't access the disk by any means. // Let's throw everything away and give up. Globals::Config::directory = "/dev/"; Globals::Config::file = "/dev/null"; return; } // Default Input configurationa InputManager::bind("left", KEY_LEFT); InputManager::bind("right", KEY_RIGHT); InputManager::bind("up", KEY_UP); InputManager::bind("down", KEY_DOWN); InputManager::bind("pause", 'p'); InputManager::bind("help", 'h'); InputManager::bind("quit", 'q'); /// HACK Initializing the default level file directory. /// I know this is hacky, but couldn't find another way to /// initialize it. /// BoardParser::directory = Globals::Config::directory + "levels/"; ScoreFile::directory = BoardParser::directory; /// Making sure they both exist...! if (! Utils::File::isDirectory(BoardParser::directory)) Utils::File::mkdir_p(BoardParser::directory); } void Globals::exit() { if (! Globals::Error::has_config_file) { std::cout << "Warning: We could not create the configuration file.\n" << " Please check permissions to the path:\n" << " " + Globals::Config::file << std::endl; } if (! Globals::Error::has_score_file) { std::cout << "Warning: We could not create the score file.\n" << " Please check permissions to the path:\n" << " " + Globals::Config::scoresFile << std::endl; } if (Globals::Error::old_version_score_file) { std::cout << "Warning: Your high score file is from an old nsnake version." << std::endl; } if (Globals::Error::strange_score_file) { // Erasing high scores... Utils::File::create(Globals::Config::scoresFile); std::cout << "Error: Corrupted high score file!\n" << " We're sorry, but we had to erase it" << std::endl; } } void Globals::loadFile() { // Now, back on track if (! Utils::File::exists(Globals::Config::file)) return; INI::Parser* ini = NULL; try { ini = new INI::Parser(Globals::Config::file); } catch(std::runtime_error& e) { // File doesn't exist (or we couldn't access it) // Either way, ignore it silently SAFE_DELETE(ini); return; } // Will be used on this macro below std::string buffer = ""; // Small macro to avoid unnecessary typing. // // To get something from the ini file we send the // text (to identify some value) and the default // value in case it doesn't exist. // // For the last one I send the variable itself, // so we fallback to the default values. #define INI_GET(var, out, in) \ { \ buffer = (* ini)(out)[in]; \ if (! buffer.empty()) \ { \ Utils::String::convert(buffer, var); \ } \ } INI_GET(Globals::Screen::center_horizontally, "screen", "center_horizontal"); INI_GET(Globals::Screen::center_vertically, "screen", "center_vertical"); INI_GET(Globals::Screen::show_borders, "screen", "borders"); INI_GET(Globals::Screen::fancy_borders, "screen", "fancy_borders"); INI_GET(Globals::Screen::outer_border, "screen", "outer_border"); INI_GET(Globals::Game::random_walls, "game", "random_walls"); INI_GET(Globals::Game::fruits_at_once, "game", "fruits_at_once"); INI_GET(Globals::Game::teleport, "game", "teleport"); INI_GET(Globals::Game::board_scroll_delay, "game", "board_scroll_delay"); INI_GET(Globals::Game::board_scroll_up, "game", "board_scroll_up"); INI_GET(Globals::Game::board_scroll_down, "game", "board_scroll_down"); INI_GET(Globals::Game::board_scroll_left, "game", "board_scroll_left"); INI_GET(Globals::Game::board_scroll_right, "game", "board_scroll_right"); // unsigned ints are the exception - their overloading // is ambiguous... I should consider removing them altogether buffer = (* ini)("game")["starting_speed"]; if (! buffer.empty()) { int starting_speed = Globals::Game::starting_speed; Utils::String::convert(buffer, starting_speed); Globals::Game::starting_speed = starting_speed; } // Special Cases // Getting input keys std::string tmp; INI_GET(tmp, "input", "left"); InputManager::bind("left", InputManager::stringToKey(tmp)); INI_GET(tmp, "input", "right"); InputManager::bind("right", InputManager::stringToKey(tmp)); INI_GET(tmp, "input", "up"); InputManager::bind("up", InputManager::stringToKey(tmp)); INI_GET(tmp, "input", "down"); InputManager::bind("down", InputManager::stringToKey(tmp)); INI_GET(tmp, "input", "pause"); InputManager::bind("pause", InputManager::stringToKey(tmp)); INI_GET(tmp, "input", "help"); InputManager::bind("help", InputManager::stringToKey(tmp)); INI_GET(tmp, "input", "quit"); InputManager::bind("quit", InputManager::stringToKey(tmp)); // Board Size int board_size = 2; INI_GET(board_size, "game", "board_size"); Globals::Game::board_size = Globals::Game::intToBoardSize(board_size); SAFE_DELETE(ini); } void Globals::saveFile() { // Even if the file doesn't exist, we'll create it. INI::Parser* ini; try { ini = new INI::Parser(Globals::Config::file); } catch(std::runtime_error& e) { ini->create(); } // Will be used on this macro below std::string buffer; // Other macro to avoid typing, similar to the one // at loadFile() #define INI_SET(out, in, var) \ { \ buffer = Utils::String::toString(var); \ ini->top().addGroup(out); \ (* ini)(out).addKey(in, buffer); \ } INI_SET("screen", "center_horizontal", Globals::Screen::center_horizontally); INI_SET("screen", "center_vertical", Globals::Screen::center_vertically); INI_SET("screen", "borders", Globals::Screen::show_borders); INI_SET("screen", "fancy_borders", Globals::Screen::fancy_borders); INI_SET("screen", "outer_border", Globals::Screen::outer_border); INI_SET("game", "random_walls", Globals::Game::random_walls); INI_SET("game", "fruits_at_once", Globals::Game::fruits_at_once); INI_SET("game", "teleport", Globals::Game::teleport); INI_SET("game", "board_scroll_delay", Globals::Game::board_scroll_delay); INI_SET("game", "board_scroll_up", Globals::Game::board_scroll_up); INI_SET("game", "board_scroll_down", Globals::Game::board_scroll_down); INI_SET("game", "board_scroll_left", Globals::Game::board_scroll_left); INI_SET("game", "board_scroll_right", Globals::Game::board_scroll_right); // unsigned ints are the exception - their overloading // is ambiguous... I should consider removing them altogether int starting_speed = Globals::Game::starting_speed; buffer = Utils::String::toString(starting_speed); ini->top().addGroup("game"); (* ini)("game").addKey("starting_speed", buffer); // Special Cases // Input Keys std::string key; key = InputManager::keyToString(InputManager::getBind("left")); INI_SET("input", "left", key); key = InputManager::keyToString(InputManager::getBind("right")); INI_SET("input", "right", key); key = InputManager::keyToString(InputManager::getBind("up")); INI_SET("input", "up", key); key = InputManager::keyToString(InputManager::getBind("down")); INI_SET("input", "down", key); key = InputManager::keyToString(InputManager::getBind("pause")); INI_SET("input", "pause", key); key = InputManager::keyToString(InputManager::getBind("help")); INI_SET("input", "help", key); key = InputManager::keyToString(InputManager::getBind("quit")); INI_SET("input", "quit", key); // Board size int board_size = Globals::Game::boardSizeToInt(Globals::Game::board_size); INI_SET("game", "board_size", board_size); try { ini->saveAs(Globals::Config::file); } catch(std::runtime_error& e) { // Couldn't save the file... // Silently return SAFE_DELETE(ini); return; } SAFE_DELETE(ini); } nSnake-3.0.1/src/Config/Globals.hpp000066400000000000000000000060271236634675000170430ustar00rootroot00000000000000#ifndef GLOBALS_H_DEFINED #define GLOBALS_H_DEFINED #include #include // Avoiding cyclic #includes struct ScoreEntry; /// All global settings to the game. /// namespace Globals { /// Allocates necessary variables. /// /// @note No need for Globals::exit() because the /// Operational System always frees the memory /// when quitting the program. /// And definitely this module will need to /// be accessed until the end of the program. /// void init(); /// Warns the user about any errors and warnings found /// during the program's execution. /// /// @note You must call this _after_ finishing up nCurses, /// otherwise things will get messed up on the terminal. void exit(); /// Loads configuration from the default file name. void loadFile(); /// Saves current configurations to the default file name. void saveFile(); // Accessing version numbers - version[MAJOR] for example #define MAJOR 0 #define MINOR 1 #define PATCH 2 /// Game version (format MMP - Major Minor Patch). /// /// On the Makefile we define a constant VERSION which is /// a string like "2.0.8". /// It contains the current game version on MAJOR.MINOR.PATCH /// format. /// /// This variable contains the same info, but without the /// dots. extern char version[3]; namespace Config { /// Root directory where we place configurations. /// /// It's `~/.local/share/nsnake/`. /// /// @note It has a trailing '/'. extern std::string directory; /// Main file where global settings reside. /// /// It's `~/.local/share/nsnake/settings.ini`. extern std::string file; /// Main file where the high scores are located. /// /// It's `~/.local/share/nsnake/scores.bin`. extern std::string scoresFile; }; namespace Screen { extern bool center_horizontally; extern bool center_vertically; extern bool show_borders; extern bool fancy_borders; extern bool outer_border; }; namespace Game { extern unsigned int starting_speed; extern int fruits_at_once; extern bool random_walls; extern bool teleport; // The board size enum BoardSize { SMALL, MEDIUM, LARGE }; BoardSize intToBoardSize(int val); int boardSizeToInt(BoardSize size); extern BoardSize board_size; extern int board_scroll_delay; extern bool board_scroll_up; extern bool board_scroll_down; extern bool board_scroll_left; extern bool board_scroll_right; /// Name of the level the game should load. /// Also, name of the current level. extern std::string current_level; }; namespace Theme { extern ColorPair text; extern ColorPair hilite_text; extern ColorPair textbox; }; // Flags to warn the user of some error at the end // of execution. namespace Error { /// We could create config file. extern bool has_config_file; /// We could create score file. extern bool has_score_file; /// Incompatible score file. extern bool old_version_score_file; /// Bad-formatted score file. extern bool strange_score_file; }; }; #endif //GLOBALS_H_DEFINED nSnake-3.0.1/src/Config/INI.cpp000066400000000000000000000107551236634675000160750ustar00rootroot00000000000000#include #include void INI::Level::addGroup(std::string name) { name = Utils::String::trim(name); // When we call `this->sections[name]` we already create // a new one if it doesn't exist. // // The only way we'll know we just made a new one is if // it's parent is NULL. // // Since the only one allowed to be like this is the top // (and the top always exist), this is the way we check // for newly-created group. if (this->sections[name].parent != NULL) return; // Setting it's parent as ourselves this->sections[name].parent = this; this->sections[name].depth = this->depth + 1; // Registering it on ourselves this->ordered_sections.push_back(this->sections.find(name)); } void INI::Level::addKey(std::string name, std::string value) { name = Utils::String::trim(name); value = Utils::String::trim(value); std::pair res = this->values.insert(std::make_pair(name, value)); if (!res.second) { // Found other key with this name, // let's overwrite its value this->values[name] = value; return; } this->ordered_values.push_back(res.first); } void INI::Parser::raise_error(std::string msg) { std::string buffer = ("Error '" + msg + "' on line #" + Utils::String::toString(this->lines)); throw std::runtime_error(buffer); } INI::Parser::Parser() : lines(0) { this->create(); } INI::Parser::Parser(std::string filename) : input_file(filename.c_str()), input(&input_file), lines(0) { if (! input) throw std::runtime_error("Failed to open file: " + filename); parse(this->top_level); } INI::Parser::Parser(std::istream& stream) : input(&stream), lines(0) { parse(this->top_level); } INI::Level& INI::Parser::top() { return this->top_level; } void INI::Parser::dump(std::ostream& stream) { dump(stream, top(), ""); } void INI::Parser::parseLevelLine(std::string& sname, size_t& depth) { depth = 0; for (; depth < line_.length(); ++depth) if (line_[depth] != '[') break; sname = line_.substr(depth, line_.length() - 2*depth); } void INI::Parser::parse(INI::Level& level) { while (std::getline(*input, line_)) { ++lines; if (line_[0] == '#' || line_[0] == ';') continue; line_ = Utils::String::trim(line_); if (line_.empty()) continue; if (line_[0] == '[') { size_t depth; std::string sname; parseLevelLine(sname, depth); INI::Level* level_current = NULL; INI::Level* parent = &level; if (depth > level.depth + 1) raise_error("section with wrong depth"); if (level.depth == depth - 1) level_current = &level.sections[sname]; else { level_current = level.parent; size_t n = (level.depth - depth); for (size_t i = 0; i < n; ++i) level_current = level_current->parent; parent = level_current; level_current = &level_current->sections[sname]; } if (level_current->depth != 0) raise_error("duplicate section name on the same level"); if (!level_current->parent) { level_current->depth = depth; level_current->parent = parent; } parent->ordered_sections.push_back(parent->sections.find(sname)); parse(*level_current); } else { // Not a group - found a key-value pair, like: // `something = other_something` size_t pos = line_.find('='); if (pos == std::string::npos) raise_error("no '=' found"); std::string key = line_.substr(0, pos); std::string value = line_.substr((pos + 1), (line_.length()-pos-1)); level.addKey(key, value); } } } void INI::Parser::dump(std::ostream& s, const INI::Level& l, const std::string& sname) { if (!sname.empty()) s << '\n'; for (size_t i = 0; i < l.depth; ++i) s << '['; if (!sname.empty()) s << sname; for (size_t i = 0; i < l.depth; ++i) s << ']'; if (!sname.empty()) s << std::endl; for (INI::Level::Values::const_iterator it = l.ordered_values.begin(); it != l.ordered_values.end(); ++it) s << (*it)->first << '=' << (*it)->second << std::endl; for (INI::Level::Sections::const_iterator it = l.ordered_sections.begin(); it != l.ordered_sections.end(); ++it) { assert((*it)->second.depth == l.depth+1); dump(s, (*it)->second, (*it)->first); } } void INI::Parser::saveAs(std::string filename) { std::ofstream file_out(filename.c_str()); if (!file_out) throw std::runtime_error(std::string("Couldn't open '" + filename + "'")); this->dump(file_out); } void INI::Parser::create() { this->top_level = Level(); } nSnake-3.0.1/src/Config/INI.hpp000066400000000000000000000131631236634675000160760ustar00rootroot00000000000000#ifndef INI_H_DEFINED #define INI_H_DEFINED #include #include #include #include #include #include #include #include /// Simple module that contains everything needed to load /// and parse a file with the INI configuration format. /// /// It basically is an INI::Parser, which uses several INI::Level /// according to the contents of the file. /// /// ## Usage /// /// For an INI file like this: /// /// key = value /// [group] /// other_key = other_value /// [[nested_group]] /// final_key = final_value /// /// You'd run this module like this: /// /// // Loads and parses immediately the file /// INI::Parser parser("path/to/file.ini"); /// // Returns "value" /// parser["key"]; /// // Returns "other_value" /// parser("group")["other_key"]; /// // Returns "final_value" /// parser("group")("nested_group")["final_key"]; /// /// ## Credits /// /// This module is a modified version of `ini-parser`, /// originally made by Poordeveloper. /// /// - Homepage: https://github.com/Poordeveloper/ini-parser /// - DevHomepage: https://github.com/Poordeveloper /// namespace INI { /// Contains a "scope" of the INI file. /// /// Suppose an INI file like this: /// /// key=value /// [group] /// key=value /// [[group2]] /// key=value /// [[[group3]] /// key=value /// /// We have four Levels, `group`, `group2` and `group3` /// and the top level, which contains all other levels. /// /// You access keys with the `[]` operator and child groups /// with the `()` operator. /// So, for the example above: /// /// parser.top()("group")("group2")("group3")["key"] == "value" /// struct Level { /// Create the topmost Level. Level() : parent(NULL), depth(0) { } /// Create a level with parent #p. Level(Level* p) : parent(p), depth(0) { } /// The parent Level of this one. /// NULL for the topmost. Level* parent; /// Counter of how many nested levels this one is. size_t depth; typedef std::map ValueMap; typedef std::map SectionMap; typedef std::list Values; typedef std::list Sections; /// All the key values inside this Level. /// So for an INI like this: /// /// [group] /// key=value /// /// This would return "value": /// /// level.values["key"] /// ValueMap values; /// All the Levels inside this Level. /// So for an INI like this: /// /// [group] /// [group2] /// key=value /// /// This would return "value": /// /// level.sections["group2"].values["key"] /// SectionMap sections; /// All values in the original order of the INI file. Values ordered_values; /// All Sections in the original order of the INI file. Sections ordered_sections; /// Access a key within this Level. const std::string& operator[](const std::string& name) { return this->values[name]; } /// Access another Level within this Level. Level& operator()(const std::string& name) { return this->sections[name]; } /// Creates a new child group with #name. /// @note If it already exists, do nothing. /// @note Inside this method we trim `name` /// of spaces and tabs. void addGroup(std::string name); /// Creates a new key #name with #value. /// /// @note If the key already exists will overwrite it's value. /// @note Inside this method we trim `name` and `value` /// of spaces and tabs. void addKey(std::string name, std::string value); }; /// Loads, reads and parses the contents of an INI file (or string). /// class Parser { public: /// Creates a blank new INI file. /// /// @see Parser::create() Parser(); /// Load and parse #filename. Parser(std::string filename); /// Parse a stream. /// It can be used to parse strings from memory. Parser(std::istream& stream); /// Outputs the contents of the INI file to #stream. /// /// It dumps a valid INI file, according to this /// parsers modifications. /// void dump(std::ostream& stream); /// Returns the top level of this INI file. /// You can then access all it's keys and nested groups /// with the Level methods. /// /// @see Level Level& top(); /// Shortcut to access a key within the top level. const std::string& operator[](const std::string& name) { return this->top()[name]; } /// Shortcut to access a Level within the top level. Level& operator()(const std::string& name) { return this->top()(name); } /// Creates a blank INI registry. /// /// It resets itself, allowing you to create brand new /// INI files from scratch. /// /// @see Level::addKey() /// @see level::addGroup() /// /// @note It drops everything that may already have /// been loaded. void create(); /// Save all the internal INI contents on a file with #filename. void saveAs(std::string filename); private: void dump(std::ostream& s, const Level& l, const std::string& sname); void parse(Level& l); /// Parses a line that defines a Level. /// /// Lines like this: /// /// [group] /// [[group]] /// /// And so on... /// void parseLevelLine(std::string& sname, size_t& depth); /// Throws an exception with error message #msg. /// It also contains the current line number. void raise_error(std::string msg); Level top_level; std::ifstream input_file; std::istream* input; std::string line_; /// Counter of how many lines we've parsed. size_t lines; }; } #endif // INI_H_DEFINED nSnake-3.0.1/src/Flow/000077500000000000000000000000001236634675000144445ustar00rootroot00000000000000nSnake-3.0.1/src/Flow/GameState.hpp000066400000000000000000000045731236634675000170400ustar00rootroot00000000000000#ifndef GAMESTATE_H_DEFINED #define GAMESTATE_H_DEFINED /// Abstract definition of a game state. /// /// ## For dummies /// /// A game state is a certain well-defined way the game behaves. /// /// Like the main menu (a state), for example. From there, /// the player can start the actual game (another state) and, /// show the pause menu (even another state). /// /// Examples include the game over screen, high score screen, /// cutscenes, etc. /// /// ## For developers /// /// All the other game states inherit from this baby. /// Each one must implement at least four methods - *load()*, /// *unload()*, *update()* and *draw()*. /// /// Each state controls the game flow via return values of /// the *update()* method. If, for instance, the player dies /// during the game, it should return *GAME_OVER* or *QUIT*. /// /// A state can also comunicate with the next one by returning /// a value from *unload()*. That value will be passed on the /// next state's *load()* and it should know what to do with it. /// class GameState { public: /// All possible transitions between states. /// /// They are used to change from one state to the other. /// The current state returns this at *update()* and the /// *StateManager* makes the appropriate changes. enum StateCode { // Internal codes for quitting and continuing QUIT, CONTINUE, // The actual game screens MAIN_MENU, GAME_START, GAME_OVER }; // Left this here just because. virtual ~GameState() {}; /// Where every state initializes it's resources. /// /// The *stack* is the previous state's returned value /// from *unload()*, allowing a state to communicate with /// the next one. virtual void load(int stack=0) = 0; /// Where every state destroys it's resources. /// /// The returned value will be sent to the next state's /// *load()* so we can send a specific message to it. virtual int unload() = 0; /// Called every frame, where states calculate everything /// that can change. /// /// The returned value will be checked by the *StateManager* /// to see if we must change the current state - if so, /// which one should we go next. virtual StateCode update() = 0; /// Called every frame, where states draw stuff on screen. virtual void draw() = 0; private: }; #endif //GAMESTATE_H_DEFINED nSnake-3.0.1/src/Flow/GameStateGame.cpp000066400000000000000000000033141236634675000176150ustar00rootroot00000000000000#include #include #include #include #include #include #include GameStateGame::GameStateGame(): game(NULL), willQuit(false) { } GameStateGame::~GameStateGame() { } void GameStateGame::load(int stack) { UNUSED(stack); try { this->game = new Game(); this->game->start(Globals::Game::current_level); this->game->scores->load(); } catch (BoardParserException& e) { Dialog::show("Couldn't load the level! (Error: \"" + e.message + "\")", true); this->willQuit = true; } catch (ScoreFileException& e) { // Show a non-intrusive dialog with why // we couldn't open high score file //e.message } catch (std::runtime_error& e) { // Some error happened during INI parsing... // What should we do? } } int GameStateGame::unload() { SAFE_DELETE(this->game); return 0; } GameState::StateCode GameStateGame::update() { if (this->willQuit) return GameState::QUIT; this->game->handleInput(); this->game->update(); if (this->game->isOver()) { // I'm touching a lot of different stuff // inside the update() function. // I know I shouldn't render things here. // Oh boy, this should be refactored away. this->game->scores->save(); Ncurses::delay_ms(500); this->game->draw(); if (Dialog::askBool("Retry?", "Game Over", true)) this->load(); // restart the game else return GameState::MAIN_MENU; } if (this->game->willQuit()) this->willQuit = true; if (this->game->willReturnToMenu()) return GameState::MAIN_MENU; return GameState::CONTINUE; } void GameStateGame::draw() { if (! this->willQuit) this->game->draw(); } nSnake-3.0.1/src/Flow/GameStateGame.hpp000066400000000000000000000027401236634675000176240ustar00rootroot00000000000000#ifndef GAMESTATEGAME_H_DEFINED #define GAMESTATEGAME_H_DEFINED #include #include /// This represents the actual game taking place. /// /// Here is defined all the game logic and rules. /// /// ## For developers: /// /// Before you continue, take a deep breath. /// /// Done? OK. /// This is the most complex class of this game and it uses /// pretty much every other class on the project. /// /// To understand it you must have a lot of patience and that /// "I'll see what it means later" feeling. /// /// Don't try to understand everything at once! /// Try to abstract a litte and follow the code thinking only in /// terms of what you think it's doing. /// /// After you've quite guessed what a method does, start looking /// into the classes that it uses. /// class GameStateGame: public GameState { public: GameStateGame(); virtual ~GameStateGame(); /// Constructs everything necessary for the game. void load(int stack=0); /// Destroys anything builded during the game int unload(); /// Updates all possible things on the game. /// /// @return A status code indicating what to do (should we /// change states/quit the game?). The codes are /// defined on GameState.hpp. GameState::StateCode update(); /// Shows everything onscreen; void draw(); private: /// The rules of the current game. Game* game; /// Keeps the game running. bool willQuit; }; #endif //GAMESTATEGAME_H_DEFINED nSnake-3.0.1/src/Flow/GameStateMainMenu.cpp000066400000000000000000000373131236634675000204630ustar00rootroot00000000000000#include #include #include #include #include #include #include #include enum NamesToEasilyIdentifyTheMenuItemsInsteadOfRawNumbers { // Main Menu ARCADE=1337, LEVELS, GAME_SETTINGS, HELP, GUI_OPTIONS, CONTROLS, QUIT_GAME, // Level select Submenu RANDOM, // Game Settings Submenu GO_BACK, STARTING_SPEED, TELEPORT, FRUITS, RANDOM_WALLS, BOARD_SIZE, SCROLL_DELAY, SCROLL_RIGHT, SCROLL_LEFT, SCROLL_UP, SCROLL_DOWN, ERASE_HIGH_SCORES, // GUI Submenu SHOW_BORDERS, FANCY_BORDERS, OUTER_BORDER, USE_COLORS, CENTER_HORIZONTAL, CENTER_VERTICAL, // Controls Submenu CONTROLS_KEY_LEFT, CONTROLS_KEY_RIGHT, CONTROLS_KEY_UP, CONTROLS_KEY_DOWN, CONTROLS_KEY_PAUSE, CONTROLS_KEY_HELP, CONTROLS_KEY_QUIT, CONTROLS_DEFAULT }; GameStateMainMenu::GameStateMainMenu(): layout(NULL), menu(NULL), menuLevels(NULL), menuLevelsActivated(false), menuGameSettings(NULL), menuGameSettingsActivated(false), menuGUIOptions(NULL), menuGUIOptionsActivated(false), menuControls(NULL), menuControlsActivated(false), helpWindows(NULL) { } void GameStateMainMenu::load(int stack) { UNUSED(stack); this->layout = new LayoutMainMenu(80, 24, this); createMainMenu(); createLevelsMenu(); createGameSettingsMenu(); createGUIOptionsMenu(); createControlsMenu(); this->helpWindows = new WindowGameHelp(); } int GameStateMainMenu::unload() { saveSettingsMenuGameSettings(); saveSettingsMenuGUIOptions(); SAFE_DELETE(this->layout); SAFE_DELETE(this->menuLevels); SAFE_DELETE(this->menuGameSettings); SAFE_DELETE(this->menuGUIOptions); SAFE_DELETE(this->menuControls); SAFE_DELETE(this->menu); return 0; } GameState::StateCode GameStateMainMenu::update() { if (InputManager::isPressed("quit")) return GameState::QUIT; if (this->menuLevelsActivated) { this->menuLevels->handleInput(); if (this->menuLevels->willQuit()) { switch (this->menuLevels->currentID()) { case GO_BACK: this->layout->menu->setTitle("Main Menu"); this->menuLevelsActivated = false; break; case RANDOM: { this->menuLevels->goRandom(); Globals::Game::current_level = this->menuLevels->current->label; return GameState::GAME_START; break; } default: // Selected a level name! Globals::Game::current_level = this->menuLevels->current->label; return GameState::GAME_START; break; } this->menuLevels->reset(); } } else if (this->menuGameSettingsActivated) { this->menuGameSettings->handleInput(); if (this->menuGameSettings->willQuit()) { saveSettingsMenuGameSettings(); // And then exit based on the selected option. switch (this->menuGameSettings->currentID()) { case ERASE_HIGH_SCORES: { bool answer = Dialog::askBool("Are you sure?"); if (answer) { ScoreFile::eraseAll(); Dialog::show("All high scores erased!", true); } } break; case GO_BACK: this->layout->menu->setTitle("Main Menu"); this->menuGameSettingsActivated = false; break; } this->menuGameSettings->reset(); } } else if (this->menuGUIOptionsActivated) { this->menuGUIOptions->handleInput(); if (this->menuGUIOptions->willQuit()) { switch(this->menuGUIOptions->currentID()) { case GO_BACK: this->layout->menu->setTitle("Main Menu"); this->menuGUIOptionsActivated = false; // Redrawing the screen to refresh settings saveSettingsMenuGUIOptions(); this->layout->windowsExit(); this->layout->windowsInit(); break; } this->menuGUIOptions->reset(); } } else if (this->menuControlsActivated) { this->menuControls->handleInput(); if (this->menuControls->willQuit()) { std::string key(""); // for key binding switch(this->menuControls->currentID()) { case GO_BACK: this->layout->menu->setTitle("Main Menu"); this->menuControlsActivated = false; break; case CONTROLS_KEY_LEFT: key = "left"; break; case CONTROLS_KEY_RIGHT: key = "right"; break; case CONTROLS_KEY_UP: key = "up"; break; case CONTROLS_KEY_DOWN: key = "down"; break; case CONTROLS_KEY_PAUSE: key = "pause"; break; case CONTROLS_KEY_HELP: key = "help"; break; case CONTROLS_KEY_QUIT: key = "quit"; break; case CONTROLS_DEFAULT: { // Reset all keybindings to default InputManager::bind("left", KEY_LEFT); InputManager::bind("right", KEY_RIGHT); InputManager::bind("up", KEY_UP); InputManager::bind("down", KEY_DOWN); InputManager::bind("pause", 'p'); InputManager::bind("help", 'h'); InputManager::bind("quit", 'q'); // Resetting the menu to show the new labels createControlsMenu(); menuControls->goLast(); break; } } // If we'll change a key binding if (! key.empty()) { Dialog::show("Press any key, Enter to Cancel"); int tmp = Ncurses::getInput(-1); if ((tmp != KEY_ENTER) && (tmp != '\n') && (tmp != ERR)) { InputManager::bind(key, tmp); MenuItemLabel* label; label = (MenuItemLabel*)menuControls->current; label->set(InputManager::keyToString(tmp)); } } this->menuControls->reset(); } } else { // We're still at the Main Menu this->menu->handleInput(); if (this->menu->willQuit()) { switch(this->menu->currentID()) { case ARCADE: // Starting game on the default level Globals::Game::current_level = ""; return GameState::GAME_START; break; case LEVELS: // Before going to the Levels menu, we must check if // the user has any levels on the level directory. // If not, we should stay at the main menu. if (BoardParser::listLevels().size() == 0) { Dialog::show("Sorry, it seems you have no levels.\n" "\n" "Please copy the default level files from\n" "`" SYSTEM_LEVEL_DIR "/`\n" "to\n" "`" + BoardParser::directory + "`\n" + "\n" "You can also download more levels from the website:\n" "http://nsnake.alexdantas.net/", true); } else { this->layout->menu->setTitle("Level Select"); this->menuLevelsActivated = true; } break; case GAME_SETTINGS: this->layout->menu->setTitle("Game Settings"); this->menuGameSettingsActivated = true; break; case GUI_OPTIONS: this->layout->menu->setTitle("GUI Options"); this->menuGUIOptionsActivated = true; break; case CONTROLS: this->layout->menu->setTitle("Controls"); this->menuControlsActivated = true; break; case HELP: this->helpWindows->run(); break; case QUIT_GAME: return GameState::QUIT; break; } this->menu->reset(); } } // Otherwise, continuing things... return GameState::CONTINUE; } void GameStateMainMenu::draw() { if (this->menuLevelsActivated) this->layout->draw(this->menuLevels); else if (this->menuGameSettingsActivated) this->layout->draw(this->menuGameSettings); else if (this->menuGUIOptionsActivated) this->layout->draw(this->menuGUIOptions); else if (this->menuControlsActivated) this->layout->draw(this->menuControls); else this->layout->draw(this->menu); } void GameStateMainMenu::createMainMenu() { SAFE_DELETE(this->menu); // Creating the Menu and Items. // Their default ids will be based on current's // settings. this->menu = new Menu(1, 1, this->layout->menu->getW() - 2, this->layout->menu->getH() - 2); MenuItem* item; item = new MenuItem("Arcade Mode", ARCADE); menu->add(item); item = new MenuItem("Level Select", LEVELS); menu->add(item); item = new MenuItem("Game Settings", GAME_SETTINGS); menu->add(item); item = new MenuItem("GUI Options", GUI_OPTIONS); menu->add(item); item = new MenuItem("Controls", CONTROLS); menu->add(item); item = new MenuItem("Help", HELP); menu->add(item); item = new MenuItem("Quit", QUIT_GAME); menu->add(item); } void GameStateMainMenu::createLevelsMenu() { SAFE_DELETE(this->menuLevels); this->menuLevels = new MenuAlphabetic(1, 1, this->layout->menu->getW() - 2, this->layout->menu->getH() - 2); MenuItem* item; std::vector levels = BoardParser::listLevels(); item = new MenuItem("Back", GO_BACK); menuLevels->add(item); item = new MenuItem("Random", RANDOM); menuLevels->add(item); menuLevels->addBlank(); for (size_t i = 0; i < levels.size(); i++) { item = new MenuItem(levels[i], i); menuLevels->add(item); } } void GameStateMainMenu::createGameSettingsMenu() { SAFE_DELETE(this->menuGameSettings); this->menuGameSettings = new Menu(1, 1, this->layout->menu->getW() - 2, this->layout->menu->getH() - 2); MenuItem* item; item = new MenuItem("Back", GO_BACK); menuGameSettings->add(item); menuGameSettings->addBlank(); MenuItemNumberbox* number; number = new MenuItemNumberbox("Starting Speed", STARTING_SPEED, 1, 10, Globals::Game::starting_speed); menuGameSettings->add(number); number = new MenuItemNumberbox("Fruits", FRUITS, 1, 99, Globals::Game::fruits_at_once); menuGameSettings->add(number); MenuItemCheckbox* check; check = new MenuItemCheckbox("Teleport", TELEPORT, Globals::Game::teleport); menuGameSettings->add(check); check = new MenuItemCheckbox("Random Walls", RANDOM_WALLS, Globals::Game::random_walls); menuGameSettings->add(check); // The board size std::vector options; options.push_back("Small"); options.push_back("Medium"); options.push_back("Large"); MenuItemTextlist* list; // the default board size std::string defaullt; switch (Globals::Game::board_size) { case Globals::Game::SMALL: defaullt = "Small"; break; case Globals::Game::MEDIUM: defaullt = "Medium"; break; default: defaullt = "Large"; break; } list = new MenuItemTextlist("Maze size", BOARD_SIZE, options, defaullt); menuGameSettings->add(list); menuGameSettings->addBlank(); number = new MenuItemNumberbox("Scroll Delay(ms)", SCROLL_DELAY, 100, 5000, Globals::Game::board_scroll_delay, 100); menuGameSettings->add(number); check = new MenuItemCheckbox("Scroll Up", SCROLL_UP, Globals::Game::board_scroll_up); menuGameSettings->add(check); check = new MenuItemCheckbox("Scroll Down", SCROLL_DOWN, Globals::Game::board_scroll_down); menuGameSettings->add(check); check = new MenuItemCheckbox("Scroll Left", SCROLL_LEFT, Globals::Game::board_scroll_left); menuGameSettings->add(check); check = new MenuItemCheckbox("Scroll Right", SCROLL_RIGHT, Globals::Game::board_scroll_right); menuGameSettings->add(check); menuGameSettings->addBlank(); item = new MenuItem("Erase High Scores", ERASE_HIGH_SCORES); menuGameSettings->add(item); } void GameStateMainMenu::createGUIOptionsMenu() { SAFE_DELETE(this->menuGUIOptions); this->menuGUIOptions = new Menu(1, 1, this->layout->menu->getW() - 2, this->layout->menu->getH() - 2); MenuItem* item; item = new MenuItem("Back", GO_BACK); menuGUIOptions->add(item); menuGUIOptions->addBlank(); MenuItemCheckbox* check; check = new MenuItemCheckbox("Show Borders", SHOW_BORDERS, Globals::Screen::show_borders); menuGUIOptions->add(check); check = new MenuItemCheckbox("Fancy Borders", FANCY_BORDERS, Globals::Screen::fancy_borders); menuGUIOptions->add(check); check = new MenuItemCheckbox("Outer Border", OUTER_BORDER, Globals::Screen::outer_border); menuGUIOptions->add(check); check = new MenuItemCheckbox("Center Horizontal", CENTER_HORIZONTAL, Globals::Screen::center_horizontally); menuGUIOptions->add(check); check = new MenuItemCheckbox("Center Vertical", CENTER_VERTICAL, Globals::Screen::center_vertically); menuGUIOptions->add(check); } void GameStateMainMenu::createControlsMenu() { SAFE_DELETE(this->menuControls); this->menuControls = new Menu(1, 1, this->layout->menu->getW() - 2, this->layout->menu->getH() - 2); MenuItem* item; item = new MenuItem("Back", GO_BACK); menuControls->add(item); menuControls->addBlank(); MenuItemLabel* label; std::string str; str = InputManager::keyToString(InputManager::getBind("up")); label = new MenuItemLabel("Key up", CONTROLS_KEY_UP, str); menuControls->add(label); str = InputManager::keyToString(InputManager::getBind("down")); label = new MenuItemLabel("Key down", CONTROLS_KEY_DOWN, str); menuControls->add(label); str = InputManager::keyToString(InputManager::getBind("left")); label = new MenuItemLabel("Key left", CONTROLS_KEY_LEFT, str); menuControls->add(label); str = InputManager::keyToString(InputManager::getBind("right")); label = new MenuItemLabel("Key right", CONTROLS_KEY_RIGHT, str); menuControls->add(label); str = InputManager::keyToString(InputManager::getBind("pause")); label = new MenuItemLabel("Key pause", CONTROLS_KEY_PAUSE, str); menuControls->add(label); str = InputManager::keyToString(InputManager::getBind("help")); label = new MenuItemLabel("Key help", CONTROLS_KEY_HELP, str); menuControls->add(label); str = InputManager::keyToString(InputManager::getBind("quit")); label = new MenuItemLabel("Key quit", CONTROLS_KEY_QUIT, str); menuControls->add(label); menuControls->addBlank(); item = new MenuItem("Reset to Defaults", CONTROLS_DEFAULT); menuControls->add(item); } void GameStateMainMenu::saveSettingsMenuGUIOptions() { if (!this->menuGUIOptions) return; // User selected an option // Let's get ids from menu items Globals::Screen::show_borders = this->menuGUIOptions->getBool(SHOW_BORDERS); Globals::Screen::fancy_borders = this->menuGUIOptions->getBool(FANCY_BORDERS); Globals::Screen::outer_border = this->menuGUIOptions->getBool(OUTER_BORDER); Globals::Screen::center_horizontally = this->menuGUIOptions->getBool(CENTER_HORIZONTAL); Globals::Screen::center_vertically = this->menuGUIOptions->getBool(CENTER_VERTICAL); } void GameStateMainMenu::saveSettingsMenuGameSettings() { if (!this->menuGameSettings) return; // User selected an option // Let's get ids from menu items Globals::Game::starting_speed = (unsigned int)this->menuGameSettings->getInt(STARTING_SPEED); Globals::Game::fruits_at_once = this->menuGameSettings->getInt(FRUITS); Globals::Game::random_walls = this->menuGameSettings->getBool(RANDOM_WALLS); Globals::Game::teleport = this->menuGameSettings->getBool(TELEPORT); std::string tmp = this->menuGameSettings->getString(BOARD_SIZE); if (tmp == "Small") Globals::Game::board_size = Globals::Game::SMALL; else if (tmp == "Medium") Globals::Game::board_size = Globals::Game::MEDIUM; else Globals::Game::board_size = Globals::Game::LARGE; Globals::Game::board_scroll_delay = this->menuGameSettings->getInt(SCROLL_DELAY); Globals::Game::board_scroll_left = this->menuGameSettings->getBool(SCROLL_LEFT); Globals::Game::board_scroll_right = this->menuGameSettings->getBool(SCROLL_RIGHT); Globals::Game::board_scroll_up = this->menuGameSettings->getBool(SCROLL_UP); Globals::Game::board_scroll_down = this->menuGameSettings->getBool(SCROLL_DOWN); } nSnake-3.0.1/src/Flow/GameStateMainMenu.hpp000066400000000000000000000024001236634675000204550ustar00rootroot00000000000000#ifndef GAMESTATEMAINMENU_H_DEFINED #define GAMESTATEMAINMENU_H_DEFINED #include #include #include #include #include /// The Main Menu. /// class GameStateMainMenu: public GameState { friend class LayoutMainMenu; public: GameStateMainMenu(); virtual ~GameStateMainMenu() { }; void load(int stack=0); /// Gets called when we're leaving this menu. /// /// It saves all the menu settings /// (for example, game speed, board size, and such) int unload(); GameState::StateCode update(); void draw(); private: LayoutMainMenu* layout; /// The main menu. Menu* menu; MenuAlphabetic* menuLevels; bool menuLevelsActivated; Menu* menuGameSettings; bool menuGameSettingsActivated; Menu* menuGUIOptions; bool menuGUIOptionsActivated; Menu* menuControls; bool menuControlsActivated; WindowGameHelp* helpWindows; // easily create internal menus void createMainMenu(); void createGameSettingsMenu(); void createLevelsMenu(); void createGUIOptionsMenu(); void createControlsMenu(); void saveSettingsMenuGUIOptions(); void saveSettingsMenuGameSettings(); }; #endif //GAMESTATEMAINMENU_H_DEFINED nSnake-3.0.1/src/Flow/InputManager.cpp000066400000000000000000000304051236634675000175440ustar00rootroot00000000000000#include #include #include int InputManager::pressedKey = ERR; // Starting with blank value std::map InputManager::binds; void InputManager::bind(std::string name, int key) { if (name.empty() || key == ERR) return; InputManager::binds[name] = key; } void InputManager::unbind(std::string name) { InputManager::binds.erase(name); } int InputManager::getBind(std::string name) { // If #key is not binded to anything... if (InputManager::binds.find(name) == InputManager::binds.end()) return ERR; return (InputManager::binds[name]); } void InputManager::update(int delay_ms) { InputManager::pressedKey = Ncurses::getInput(delay_ms); } bool InputManager::noKeyPressed() { return (InputManager::pressedKey == ERR); } bool InputManager::isPressed(int key) { return (InputManager::pressedKey == key); } bool InputManager::isPressed(std::string key) { // If #key is not binded to anything, will return false if (InputManager::binds.find(key) == InputManager::binds.end()) return false; return (InputManager::isPressed(InputManager::binds[key])); } std::string InputManager::keyToString(int value) { // Is character inside the ASCII table? if (value >= 0 && value <= 127) { if (value == ' ') return "space"; // The "printable" part of the ASCII table - easy if (value > ' ' && value <= '~') { // Converting (int -> char -> char* -> std::string) char c[2] = { (char)value, '\0' }; return std::string(c); } // Non-printable, then... // Let's get some names switch (value) { case 0: return "null"; case 27: return "escape"; case 127: return "delete"; } } // If not, then this character is a special Ncurses value. // Those things were directy taken from // // NOTE: Wont use KEY_BREAK, KEY_SRESET, KEY_RESET, KEY_F0 // and KEY_EIC beucase they're strange.. // NOTE: Also not using KEY_MOUSE, KEY_RESIZE and KEY_EVENT // because they're Ncurses' flags for other things // than the keyboard. // switch (value) { // Special case - value for "no key pressed" case ERR: return "undefined"; case KEY_DOWN: return "down"; case KEY_UP: return "up"; case KEY_LEFT: return "left"; case KEY_RIGHT: return "right"; case KEY_HOME: return "home"; case KEY_BACKSPACE: return "backspace"; case KEY_F(1): return "f1"; case KEY_F(2): return "f2"; case KEY_F(3): return "f3"; case KEY_F(4): return "f4"; case KEY_F(5): return "f5"; case KEY_F(6): return "f6"; case KEY_F(7): return "f7"; case KEY_F(8): return "f8"; case KEY_F(9): return "f9"; case KEY_F(10): return "f10"; case KEY_F(11): return "f11"; case KEY_F(12): return "f12"; case KEY_DL: return "delete-line"; case KEY_IL: return "insert-line"; case KEY_DC: return "delete-char"; case KEY_IC: return "insert"; case KEY_CLEAR: return "clear"; case KEY_EOS: return "clear-to-end-of-screen"; case KEY_EOL: return "clear-to-end-of-line"; case KEY_SF: return "scroll-forward"; case KEY_SR: return "scroll-backward"; case KEY_NPAGE: return "page-down"; case KEY_PPAGE: return "page-up"; case KEY_STAB: return "set-tab"; case KEY_CTAB: return "clear-tab"; case KEY_CATAB: return "clear-all-tabs"; case KEY_ENTER: return "enter"; case KEY_PRINT: return "print"; case KEY_LL: return "home-down"; // wtf? case KEY_A1: return "keypad-upper-left"; case KEY_A3: return "keypad-upper-right"; case KEY_B2: return "keypad-center"; case KEY_C1: return "keypad-lower-left"; case KEY_C3: return "keypad-lower-right"; case KEY_BTAB: return "back-tab"; case KEY_BEG: return "begin"; case KEY_CANCEL: return "cancel"; case KEY_CLOSE: return "close"; case KEY_COMMAND: return "command"; // not mac/osx's case KEY_COPY: return "copy"; case KEY_CREATE: return "create"; case KEY_END: return "end"; case KEY_EXIT: return "exit"; case KEY_FIND: return "find"; case KEY_HELP: return "help"; case KEY_MARK: return "mark"; case KEY_MESSAGE: return "message"; case KEY_MOVE: return "move"; case KEY_NEXT: return "next"; case KEY_OPEN: return "open"; case KEY_OPTIONS: return "options"; case KEY_PREVIOUS: return "previous"; case KEY_REDO: return "redo"; case KEY_REFERENCE: return "reference"; case KEY_REFRESH: return "refresh"; case KEY_REPLACE: return "replace"; case KEY_RESTART: return "restart"; case KEY_RESUME: return "resume"; case KEY_SAVE: return "save"; case KEY_SBEG: return "shift-begin"; case KEY_SCANCEL: return "shift-cancel"; case KEY_SCOMMAND: return "shift-command"; case KEY_SCOPY: return "shift-copy"; case KEY_SCREATE: return "shift-create"; case KEY_SDC: return "shift-delete-char"; case KEY_SDL: return "shift-delete-line"; case KEY_SELECT: return "select"; case KEY_SEND: return "shift-end"; case KEY_SEOL: return "shift-clear-to-end-of-line"; case KEY_SEXIT: return "shift-exit"; case KEY_SFIND: return "shift-find"; case KEY_SHELP: return "shift-help"; case KEY_SHOME: return "shift-home"; case KEY_SIC: return "shift-insert"; case KEY_SLEFT: return "shift-left"; case KEY_SMESSAGE: return "shift-message"; case KEY_SMOVE: return "shift-move"; case KEY_SNEXT: return "shift-next"; case KEY_SOPTIONS: return "shift-options"; case KEY_SPREVIOUS: return "shift-previous"; case KEY_SPRINT: return "shift-print"; case KEY_SREDO: return "shift-redo"; case KEY_SREPLACE: return "shift-replace"; case KEY_SRIGHT: return "shift-right"; case KEY_SRSUME: return "shift-resume"; case KEY_SSAVE: return "shift-save"; case KEY_SSUSPEND: return "shift-suspend"; case KEY_SUNDO: return "shift-undo"; case KEY_SUSPEND: return "suspend"; case KEY_UNDO: return "undo"; default: break; } return "undefined"; } int InputManager::stringToKey(std::string string) { if (string == "space") return ' '; // Let's hope it's a single char from the ASCII table if (string.size() == 1) { char c = string.c_str()[0]; if (c > ' ' && c <= '~') return c; // undefined, sorry :( return ERR; } // Special case, unknown key if (string == "undefined") return ERR; if (string == "down") return KEY_DOWN; if (string == "up") return KEY_UP; if (string == "left") return KEY_LEFT; if (string == "right") return KEY_RIGHT; if (string == "home") return KEY_HOME; if (string == "backspace") return KEY_BACKSPACE; if (string == "f1") return KEY_F(1); if (string == "f2") return KEY_F(2); if (string == "f3") return KEY_F(3); if (string == "f4") return KEY_F(4); if (string == "f5") return KEY_F(5); if (string == "f6") return KEY_F(6); if (string == "f7") return KEY_F(7); if (string == "f8") return KEY_F(8); if (string == "f9") return KEY_F(9); if (string == "f10") return KEY_F(10); if (string == "f11") return KEY_F(11); if (string == "f12") return KEY_F(12); if (string == "delete-line") return KEY_DL; if (string == "insert-line") return KEY_IL; if (string == "delete-char") return KEY_DC; if (string == "insert") return KEY_IC; if (string == "clear") return KEY_CLEAR; if (string == "clear-to-end-of-screen") return KEY_EOS; if (string == "clear-to-end-of-line") return KEY_EOL; if (string == "scroll-forward") return KEY_SF; if (string == "scroll-backward") return KEY_SR; if (string == "page-down") return KEY_NPAGE; if (string == "page-up") return KEY_PPAGE; if (string == "set-tab") return KEY_STAB; if (string == "clear-tab") return KEY_CTAB; if (string == "clear-all-tabs") return KEY_CATAB; if (string == "enter") return KEY_ENTER; if (string == "print") return KEY_PRINT; if (string == "home-down") return KEY_LL; if (string == "keypad-upper-left") return KEY_A1; if (string == "keypad-upper-right") return KEY_A3; if (string == "keypad-center") return KEY_B2; if (string == "keypad-lower-left") return KEY_C1; if (string == "keypad-lower-right") return KEY_C3; if (string == "back-tab") return KEY_BTAB; if (string == "begin") return KEY_BEG; if (string == "cancel") return KEY_CANCEL; if (string == "close") return KEY_CLOSE; if (string == "command") return KEY_COMMAND; if (string == "copy") return KEY_COPY; if (string == "create") return KEY_CREATE; if (string == "end") return KEY_END; if (string == "exit") return KEY_EXIT; if (string == "find") return KEY_FIND; if (string == "help") return KEY_HELP; if (string == "mark") return KEY_MARK; if (string == "message") return KEY_MESSAGE; if (string == "move") return KEY_MOVE; if (string == "next") return KEY_NEXT; if (string == "open") return KEY_OPEN; if (string == "options") return KEY_OPTIONS; if (string == "previous") return KEY_PREVIOUS; if (string == "redo") return KEY_REDO; if (string == "reference") return KEY_REFERENCE; if (string == "refresh") return KEY_REFRESH; if (string == "replace") return KEY_REPLACE; if (string == "restart") return KEY_RESTART; if (string == "resume") return KEY_RESUME; if (string == "save") return KEY_SAVE; if (string == "shift-begin") return KEY_SBEG; if (string == "shift-cancel") return KEY_SCANCEL; if (string == "shift-command") return KEY_SCOMMAND; if (string == "shift-copy") return KEY_SCOPY; if (string == "shift-create") return KEY_SCREATE; if (string == "shift-delete-char") return KEY_SDC; if (string == "shift-delete-line") return KEY_SDL; if (string == "select") return KEY_SELECT; if (string == "shift-end") return KEY_SEND; if (string == "shift-clear-to-end-of-line") return KEY_SEOL; if (string == "shift-exit") return KEY_SEXIT; if (string == "shift-find") return KEY_SFIND; if (string == "shift-help") return KEY_SHELP; if (string == "shift-home") return KEY_SHOME; if (string == "shift-insert") return KEY_SIC; if (string == "shift-left") return KEY_SLEFT; if (string == "shift-message") return KEY_SMESSAGE; if (string == "shift-move") return KEY_SMOVE; if (string == "shift-next") return KEY_SNEXT; if (string == "shift-options") return KEY_SOPTIONS; if (string == "shift-previous") return KEY_SPREVIOUS; if (string == "shift-print") return KEY_SPRINT; if (string == "shift-redo") return KEY_SREDO; if (string == "shift-replace") return KEY_SREPLACE; if (string == "shift-right") return KEY_SRIGHT; if (string == "shift-resume") return KEY_SRSUME; if (string == "shift-save") return KEY_SSAVE; if (string == "shift-suspend") return KEY_SSUSPEND; if (string == "shift-undo") return KEY_SUNDO; if (string == "suspend") return KEY_SUSPEND; if (string == "undo") return KEY_UNDO; // Undefined key :( return ERR; } nSnake-3.0.1/src/Flow/InputManager.hpp000066400000000000000000000027161236634675000175550ustar00rootroot00000000000000#ifndef INPUTMANAGER_H_DEFINED #define INPUTMANAGER_H_DEFINED #include #include /// namespace InputManager { /// Attaches #name to #key. void bind(std::string name, int key); /// Removes all keybindings for #name. void unbind(std::string name); /// Returns the key that's bound to #name. int getBind(std::string name); /// Tells if no key was actually pressed on the last frame. bool noKeyPressed(); /// Tells if #key was pressed. /// @note It's the Ncurses' internal value. bool isPressed(int key); /// Tells if #key was pressed. /// @note It's the user-defined key binding. bool isPressed(std::string key); /// Gets the input. /// /// * If negative, will wait forever for an input. /// * If 0, will return immediately, wether a key /// was pressed or not. /// * If positive, will wait for #delay_ms milliseconds. void update(int delay_ms=0); /// Returns human-readable name for internal value #key. std::string keyToString(int key); /// Returns the internal value for nameable #string key. int stringToKey(std::string string); /// The key that was pressed on the last frame. /// /// It's an Ncurses internal value, being the ASCII /// value or some special others - like KEY_LEFT, /// KEY_RIGHT and such. /// /// Avoid using it, prefer prefer #isPressed(). extern int pressedKey; /// Contains all binds from names to internal Ncurses values. extern std::map binds; }; #endif //INPUTMANAGER_H_DEFINED nSnake-3.0.1/src/Flow/StateManager.cpp000066400000000000000000000036071236634675000175310ustar00rootroot00000000000000#include #include #include #include #include #include StateManager::StateManager(): currentState(NULL), sharedInfo(0) { // The first state, Hardcoded this->currentState = new GameStateMainMenu(); this->currentState->load(); } StateManager::~StateManager() { if (this->currentState) this->currentState->unload(); SAFE_DELETE(this->currentState); } void StateManager::run() { bool letsQuit = false; while (!letsQuit) { InputManager::update(); // Updating the whole state. // This value is returned from it tell us if // we need to switch from the current state. GameState::StateCode whatToDoNow; whatToDoNow = this->currentState->update(); switch (whatToDoNow) { case GameState::CONTINUE: // Just continue on the current state. break; case GameState::QUIT: this->currentState->unload(); delete this->currentState; this->currentState = NULL; letsQuit = true; break; case GameState::GAME_START: { this->currentState->unload(); delete this->currentState; this->currentState = new GameStateGame(); this->currentState->load(); break; } case GameState::MAIN_MENU: { this->currentState->unload(); delete this->currentState; this->currentState = new GameStateMainMenu(); this->currentState->load(); break; } default: break; } if (this->currentState) this->currentState->draw(); Utils::Time::delay_ms(100); } // // Right before quitting, we must save current // // user's settings // Globals::saveSettings(); // // And set the current profile as the default // // to load next time. // INI ini; // if (! ini.load(Globals::Config::file)) // ini.create(); // ini.set("profiles:default", Globals::Profiles::current->name); // ini.save(Globals::Config::file); } nSnake-3.0.1/src/Flow/StateManager.hpp000066400000000000000000000025551236634675000175370ustar00rootroot00000000000000#ifndef STATEMANAGER_H_DEFINED #define STATEMANAGER_H_DEFINED #include /// Giga-class that switches from game states. /// /// It makes the transitions between them, assuring each one is /// properly initialized. /// /// ## For developers: /// /// Short and simple explanation: /// /// * Creates the first state (allocating everything). /// * Run it (updating and drawing). /// * Whenever the state feels like changing, it will tell us /// (quit, for example). /// * Then we must delete the current state and repeat this whole /// process for the next one. /// class StateManager { public: /// Initializes pretty much everything. StateManager(); virtual ~StateManager(); /// Main entry point and game loop. /// /// This is where it all happens. The game never leaves this /// method, the only thing that's allowed to happen are /// state-specific methods called inside here. /// /// If we leave this method, the game quits, as seen on /// *main.cpp*. void run(); private: /// Current game state - defines what will actually happen. GameState* currentState; /// Shared information between states. /// /// If a state want to share something with another, it should /// return a value that will be stored right here. /// /// Perhaps I should make this a template class or something. int sharedInfo; }; #endif /* STATEMANAGER_H_DEFINED */ nSnake-3.0.1/src/Game/000077500000000000000000000000001236634675000144065ustar00rootroot00000000000000nSnake-3.0.1/src/Game/Board.cpp000066400000000000000000000126741236634675000161530ustar00rootroot00000000000000#include #include #include #include int Board::small_width = 40; int Board::small_height = 10; int Board::medium_width = 55; int Board::medium_height = 14; int Board::large_width = 78; int Board::large_height = 21; Board::Board(int width, int height, Style style): style(style), start_x(BOARD_DEFAULT_PLAYER_X), start_y(BOARD_DEFAULT_PLAYER_Y) { this->board = new Array2D(width, height); this->clear(); } Board::~Board() { delete this->board; } bool Board::isBorder(int x, int y) { if ((x == 0) || (x == (int)this->board->width() - 1) || (y == 0) || (y == (int)this->board->height() - 1)) return true; return false; } bool Board::isWall(int x, int y) { if (isBorder(x, y)) { // If we can teleport, the borders are // not collidable - we'll just walk through them. return (this->style == Board::SOLID); } return (this->board->at(x, y)); } int Board::getW() { return this->board->width(); } int Board::getH() { return this->board->height(); } void Board::draw(Window* win) { int teleport_appearance = '\''; int solid_appearance = ((Globals::Screen::fancy_borders) ? ACS_CKBOARD : '#'); for (size_t i = 0; i < (this->board->width()); i++) { for (size_t j = 0; j < (this->board->height()); j++) { if (this->isBorder(i, j)) { win->printChar(((this->style == Board::TELEPORT) ? teleport_appearance : solid_appearance), i, j, 0); continue; } else if (this->isWall(i, j)) win->printChar(solid_appearance, i, j, 0); } } } void Board::randomlyFillExceptBy(int x, int y) { for (size_t i = 0; i < (this->board->width()); i++) { for (size_t j = 0; j < (this->board->height()); j++) { // The frequency of random Walls (cute PI) if (Utils::Random::booleanWithChance(0.031415923)) this->board->set(i, j, true); } } // Clearing some space for #x and #y for (int i = -2; i != 7; i++) this->board->set(x + i, y, false); } void Board::teleport(Player* player) { // If we don't teleport, // at least keep it on the current position int newx = player->getX(); int newy = player->getY(); // Where we'll place the player int left = 1; int right = this->board->width() - 2; if (player->getX() < left) { newx = right; } else if (player->getX() > right) { newx = left; } int top = 1; int bottom = this->board->height() - 2; if (player->getY() < top) { newy = bottom; } else if (player->getY() > bottom) { newy = top; } player->moveTo(newx, newy); } void Board::clear() { // Making it empty for (size_t i = 0; i < this->board->width(); i++) for (size_t j = 0; j < this->board->height(); j++) this->board->set(i, j, false); } void Board::setBoard(std::vector >& newBoard) { // Making it empty for (size_t i = 0; i < this->board->width(); i++) for (size_t j = 0; j < this->board->height(); j++) this->board->set(i, j, newBoard[j][i]); } int Board::getStartX() { return this->start_x; } int Board::getStartY() { return this->start_y; } void Board::setStartX(int x) { this->start_x = x; } void Board::setStartY(int y) { this->start_y = y; } void Board::setMetadata(std::string name, std::string value) { this->metadata[name] = value; } std::string Board::getMetadata(std::string name) { if (! this->hasMetadata(name)) return ""; return this->metadata[name]; } bool Board::hasMetadata(std::string name) { return (this->metadata.find(name) != this->metadata.end()); } void Board::scrollLeft() { // Going line by line from top to bottom for (size_t j = 0; j < this->board->height() - 1; j++) { // Get first left element from this line bool tmp = this->board->at(1, j); // Shifting all elements one block left for (size_t i = 0; i < (this->board->width() - 1); i++) this->board->set(i, j, this->board->at(i + 1, j)); // Putting the first element on the last place this->board->set(this->board->width() - 2, j, tmp); } } void Board::scrollRight() { // Going line by line from top to bottom for (size_t j = 0; j < this->board->height() - 1; j++) { // Get first right element from this line bool tmp = this->board->at(this->board->width() - 2, j); // Shifting all elements one block right for (size_t i = (this->board->width() - 1); i > 0; i--) this->board->set(i, j, this->board->at(i - 1, j)); // Putting the first element on the last place this->board->set(1, j, tmp); } } void Board::scrollUp() { // Going line by line from left to right for (size_t j = 0; j < this->board->width() - 1; j++) { // Get first top element from this line bool tmp = this->board->at(j, 1); // Shifting all elements one block up for (size_t i = 0; i < (this->board->height() - 1); i++) this->board->set(j, i, this->board->at(j, i + 1)); // Putting the first element on the last place this->board->set(j, this->board->height() - 2, tmp); } } void Board::scrollDown() { // Going line by line from left to right for (size_t j = 0; j < this->board->width() - 1; j++) { // Get first bottom element from this line bool tmp = this->board->at(j, this->board->height() - 2); // Shifting all elements one block down for (size_t i = this->board->height() - 2; i > 0; i--) this->board->set(j, i, this->board->at(j, i - 1)); // Putting the first element on the last place this->board->set(j, 1, tmp); } } nSnake-3.0.1/src/Game/Board.hpp000066400000000000000000000063131236634675000161510ustar00rootroot00000000000000#ifndef BOARD_H_DEFINED #define BOARD_H_DEFINED #include #include #include // Avoiding circular #include hell. class Player; // Default starting point for // the player on every level #define BOARD_DEFAULT_PLAYER_X 2 #define BOARD_DEFAULT_PLAYER_Y 2 /// A level where the snake runs and eats fruits. /// /// @note I couldn't name this class "Level" because it could /// get confusing with the concept of game speed, which is /// also based on the word "Level". /// /// This class represents a single level the snake can play on. /// /// It is essentially a 2D matrix of tiles, where each can be /// either "Walls" or "Empty. /// /// It also contains some extra info, like the position where /// the player should spawn, and (if this level was created by /// someone) the author, date and stuff. /// class Board { public: static int small_width; static int small_height; static int medium_width; static int medium_height; static int large_width; static int large_height; /// If the player will teleport when reaching the /// Board's limits or not. enum Style { SOLID, TELEPORT }; /// Creates a new Board. /// /// @param width Whole level width /// @param height Whole level height /// @param style If the player will teleport when reaching the limits /// Board(int width, int height, Style style); virtual ~Board(); /// Tells if there's a wall at #x #y. bool isWall(int x, int y); bool isBorder(int x, int y); int getW(); int getH(); void draw(Window* win); /// Places random walls all over the Board /// except by #x and #y, allowing the Player /// to move a little bit when starting. void randomlyFillExceptBy(int x, int y); /// Makes the `Player` teleport if it's on a border. void teleport(Player* player); /// Makes the whole level empty. void clear(); /// Sets the whole level content. /// /// @param newBoard 2D matrix of booleans, telling if /// there's a "Wall" on that tile or not. /// void setBoard(std::vector >& newBoard); /// Tells if the player will teleport when /// reaching the Board's limits or not. /// /// @note The Board adapts automatically if you /// change this on-the-fly. Style style; int getStartX(); int getStartY(); void setStartX(int x); void setStartY(int y); // Things related to metadata // (author name, date made, comments, and more) /// Sets a meta information from this level. void setMetadata(std::string name, std::string value); /// Gets a meta information from this level. /// /// @return Requested info or empty string if not exists. /// std::string getMetadata(std::string name); /// Tells if this level has a specific information attached. bool hasMetadata(std::string name); void scrollLeft(); void scrollRight(); void scrollUp(); void scrollDown(); private: /// The actual level on the screen. /// `true` means there's a wall here. /// `false` means the player can walk through. Array2D* board; int start_x; ///< Where the player will start (x axis) int start_y; ///< Where the player will start (y axis) /// Contains all this level's metadata. std::map metadata; }; #endif //BOARD_H_DEFINED nSnake-3.0.1/src/Game/BoardParser.cpp000066400000000000000000000124661236634675000173270ustar00rootroot00000000000000#include #include #include #include #include #include #include // HACK This will be initialized at `Globals::init()` std::string BoardParser::directory = ""; std::string BoardParser::extension = "nsnake"; Board* BoardParser::load(std::string name) { std::string filename = (BoardParser::directory + name + "." + BoardParser::extension); return BoardParser::loadFile(filename); } Board* BoardParser::loadFile(std::string filename) { std::ifstream file(filename.c_str()); if (!(file.is_open())) throw BoardParserException("Can't open file '" + filename + "'"); // Tells what's the current line on the file // (independent of comments and empty lines) int line_count = 0; // The whole file has two parts: metadata and level definition. // // Let's read the whole file, line by line, adding the respective // parts to each of these buffers. // // We'll handle them separately later. std::string metadata_buffer; std::string level_buffer; // This will get used to the end of the // function. std::string current_line = ""; while (std::getline(file, current_line)) { ++line_count; current_line = Utils::String::trim(current_line); // We only care for the line that tells a level // definition will start. if (current_line != "start") metadata_buffer += (current_line + '\n'); else { // Yay, start of the level definition! bool parsed_level = false; while (std::getline(file, current_line)) { ++line_count; current_line = Utils::String::trim(current_line); if (current_line == "end") { parsed_level = true; break; } level_buffer += (current_line + '\n'); } if (! parsed_level) { // End-of-file... // Something wrong happened throw BoardParserException( "Abrupt ending of file while parsing level at line " + Utils::String::toString(line_count) ); } // Finished parsing the level! // Back to the metadata. } } // Now we'll analyze the level definition we just got from the file int player_start_x = 1; // It's (1, 1) because if it somehow starts int player_start_y = 1; // at (0, 0) it will always end up in a wall // and die right at the beginning // Finally, when we read the level we have // two states for each tile - "wall" or "not wall" std::vector > rawBoard; std::vector level_lines = Utils::String::split(level_buffer, '\n'); for (size_t j = 0; j < (level_lines.size()); j++) { current_line = level_lines[j]; if (current_line.empty()) continue; std::vector rawBoardLine; // And now we go through each char on the line // checking if it's a wall, blank space or the // player's starting point. // for (size_t i = 0; i < current_line.size(); i++) { if (current_line[i] == SNAKE_CHAR) { player_start_x = i; player_start_y = rawBoard.size(); // It IS an empty space, after all... rawBoardLine.push_back(false); } else rawBoardLine.push_back(current_line[i] == WALL_CHAR); } // Commit this line to the level rawBoard.push_back(rawBoardLine); } // I know it's counter-intuitive, but the width // and height is just like this int board_width = rawBoard[0].size(); int board_height = rawBoard.size(); Board* board = new Board(board_width, board_height, ((Globals::Game::teleport) ? Board::TELEPORT : Board::SOLID)); board->setBoard(rawBoard); board->setStartX(player_start_x); board->setStartY(player_start_y); // Remember that metadata up there? // Let's get every present metadata through an INI parser std::stringstream stream; stream << metadata_buffer; INI::Parser parser(stream); board->setMetadata("name", parser["name"]); board->setMetadata("author", parser["author"]); board->setMetadata("date", parser["date"]); board->setMetadata("comment", parser["comment"]); return board; } std::vector BoardParser::listLevels() { std::vector levels = Utils::File::ls(BoardParser::directory); // Remove files that doesn't end with the default file extension // // Also, don't store the full path, only it's basename // (like "file" and not "/path/to/file") // for (std::vector::iterator it = levels.begin(); it != levels.end(); ++it) { if (Utils::File::extension(*it) == BoardParser::extension) (*it) = (Utils::File::dropExtension(Utils::File::basename((*it)))); else { // When we remove an element of a vector // it points to the next element. it = levels.erase(it); // We need to decrement it because the `for` // will increment at the end --it; } } return levels; } nSnake-3.0.1/src/Game/BoardParser.hpp000066400000000000000000000034271236634675000173310ustar00rootroot00000000000000#ifndef BOARDPARSER_H_DEFINED #define BOARDPARSER_H_DEFINED #include #include #include /// Custom exception class to specify an error that /// occurred during a level loading. /// class BoardParserException : public std::exception { public: BoardParserException(std::string message): message(message) { } ~BoardParserException() throw() { } std::string message; }; #define COMMENT_CHAR ';' #define WALL_CHAR '#' #define SNAKE_CHAR '@' /// Opens, loads and parses a level file, returning a /// well-formed `Board`. /// class BoardParser { public: /// Default directory where the level files are. /// /// @note Defaults to `Globals::Config::directory + levels/`, /// which at the time of writing is `~/.local/share/nsnake/levels` static std::string directory; /// Default extension for nSnake level files. /// /// It's all the part that comes _after the dot_ on /// a file name. /// /// @note Defaults to "nsnake" /// @note Files that does not end with it will be _ignored_. static std::string extension; /// Loads and parses level with *name*. /// /// @note It looks for it on standard levels location, /// whatever that might mean. /// /// @see loadFile /// static Board* load(std::string filename); /// Loads and parses the level at *filename*. /// /// @return A new `Board` if successful, NULL if failed. /// @note Make sure to delete it later! /// static Board* loadFile(std::string filename); /// TODO static bool save(Board* board, std::string filename); /// Lists all levels found by the game. /// /// It looks on standard level locations. /// Whatever that might mean. /// static std::vector listLevels(); }; #endif //BOARDPARSER_H_DEFINED nSnake-3.0.1/src/Game/FruitManager.cpp000066400000000000000000000027361236634675000175060ustar00rootroot00000000000000#include #include FruitManager::FruitManager(int amount): amount(amount) { } bool FruitManager::eatenFruit(Player* player) { // If any fruit was eaten by #player, we'll // delete it. for (std::vector::iterator it = this->fruit.begin(); it != this->fruit.end();) { if (player->headHit((*it).x, (*it).y)) { // Alright, eaten! it = this->fruit.erase(it); return true; } else ++it; } return false; } void FruitManager::update(Player* player, Board* board) { // Creating enough fruits to fill the #amount quota. int diff = (this->amount - this->fruit.size()); if (diff > 0) for (int i = 0; i < (diff); i++) this->addRandomly(board, player); } int FruitManager::getAmount() { return (this->amount); } void FruitManager::add(int x, int y) { this->fruit.push_back(Fruit(x, y)); } void FruitManager::addRandomly(Board* board, Player* player) { int newx = 1; int newy = 1; // Creating between the board limits, // making sure it isn't inside player's body. do { newx = Utils::Random::between(1, board->getW() - 2); newy = Utils::Random::between(1, board->getH() - 2); } while (player->bodyHit(newx, newy) || board->isWall(newx, newy)); this->add(newx, newy); } void FruitManager::draw(Window* win) { for (unsigned int i = 0; i < (this->fruit.size()); i++) win->print("$", this->fruit[i].x, this->fruit[i].y, Colors::pair(COLOR_RED, COLOR_DEFAULT, true)); } nSnake-3.0.1/src/Game/FruitManager.hpp000066400000000000000000000025661236634675000175140ustar00rootroot00000000000000#ifndef FRUITMANAGER_H_DEFINED #define FRUITMANAGER_H_DEFINED #include #include #include #include /// A single fruit. struct Fruit { int x; int y; Fruit(int x, int y): x(x), y(y) { } }; /// Controls how many Fruits are there and how they're spawned. class FruitManager { public: /// Creates a Fruit container that has at most /// #amount fruits at once on the screen. FruitManager(int amount); virtual ~FruitManager() {}; /// Tells if the #player has eaten a fruit this frame. bool eatenFruit(Player* player); /// Updates internal fruits, adding them to the #board /// and making sure it doesn't touch #player. void update(Player* player, Board* board); /// Returns the maximum size we can store within /// this manager. /// /// @note This is not the current size, as you can /// use #add and #addRandomly to forcefully /// add beyond it's limit. int getAmount(); /// Creates a fruit, adding it at #x, #y. /// @note It ignores internal #amount. void add(int x, int y); /// Creates a fruit randomly within boundaries of #board, /// making sure that it's not inside #player. /// /// @note It ignores internal #amount. void addRandomly(Board* board, Player* player); void draw(Window* win); private: std::vector fruit; int amount; }; #endif //FRUITMANAGER_H_DEFINED nSnake-3.0.1/src/Game/Game.cpp000066400000000000000000000205241236634675000157660ustar00rootroot00000000000000#include #include #include #include #include #include #include // Options of the Pause Menu enum NamesToEasilyIdentifyTheMenuItemsInsteadOfRawNumbers { RESUME, RESTART, QUIT_MENU, QUIT_GAME }; Game::Game(): scores(NULL), currentScore(NULL), layout(NULL), gameOver(false), isPaused(false), showPauseMenu(false), showHelp(false), pauseMenu(NULL), player(NULL), board(NULL), fruits(NULL) { } Game::~Game() { SAFE_DELETE(this->layout); SAFE_DELETE(this->scores); SAFE_DELETE(this->currentScore); SAFE_DELETE(this->pauseMenu); SAFE_DELETE(this->player); SAFE_DELETE(this->board); SAFE_DELETE(this->fruits); } void Game::start(std::string levelName) { // Cleaning things from the previous game (if any) SAFE_DELETE(this->layout); SAFE_DELETE(this->scores); SAFE_DELETE(this->currentScore); SAFE_DELETE(this->pauseMenu); SAFE_DELETE(this->player); SAFE_DELETE(this->board); SAFE_DELETE(this->fruits); this->userAskedToQuit = false; this->userAskedToGoToMenu = false; this->gameOver = false; this->isPaused = false; this->scores = new ScoreFile(levelName); // will load the scores on `GameStateGame` this->currentScore = new ScoreEntry(); this->currentScore->level = levelName; this->currentScore->speed = Globals::Game::starting_speed; this->currentScore->fruits = Globals::Game::fruits_at_once; this->currentScore->random_walls = Globals::Game::random_walls; this->currentScore->teleport = Globals::Game::teleport; this->currentScore->board_size = Globals::Game::board_size; this->currentScore->board_scroll_delay = Globals::Game::board_scroll_delay; this->currentScore->board_scroll_left = Globals::Game::board_scroll_left; this->currentScore->board_scroll_right = Globals::Game::board_scroll_right; this->currentScore->board_scroll_up = Globals::Game::board_scroll_up; this->currentScore->board_scroll_down = Globals::Game::board_scroll_down; // Defaults to large int boardw = Board::large_width; int boardh = Board::large_height; if (Globals::Game::board_size == Globals::Game::SMALL) { boardw = Board::small_width; boardh = Board::small_height; } else if (Globals::Game::board_size == Globals::Game::MEDIUM) { boardw = Board::medium_width; boardh = Board::medium_height; } if (! levelName.empty()) this->board = BoardParser::load(levelName); else { // If no level name is specified, silently // fall back to a default one. this->board = new Board(boardw, boardh, ((Globals::Game::teleport) ? Board::TELEPORT : Board::SOLID)); } // the player! this->player = new Player(this->board->getStartX(), this->board->getStartY()); if (Globals::Game::random_walls) this->board->randomlyFillExceptBy(this->player->getX(), this->player->getY()); // fruits beibeh this->fruits = new FruitManager(Globals::Game::fruits_at_once); this->fruits->update(this->player, this->board); // Finally, the interface // // NOTE: It depends on the `currentScore` level name! // Do not initialize it before! this->layout = new LayoutGame(this, 80, 24); // Creating the menu and adding each item this->pauseMenu = new Menu(1, 1, this->layout->pause->getW() - 2, this->layout->pause->getH() - 2); MenuItem* item; item = new MenuItem("Resume", RESUME); this->pauseMenu->add(item); item = new MenuItem("Restart", RESTART); this->pauseMenu->add(item); this->pauseMenu->addBlank(); item = new MenuItem("Quit to Main Menu", QUIT_MENU); this->pauseMenu->add(item); item = new MenuItem("Quit Game", QUIT_GAME); this->pauseMenu->add(item); // Starting timers this->timerSnake.start(); this->timerBoard.start(); this->timer.start(); } void Game::handleInput() { if (InputManager::noKeyPressed()) return; // The only two absolute inputs are to quit and pause. // Others depend if the game is paused or not. if (InputManager::isPressed("quit")) { this->userAskedToQuit = true; } else if (InputManager::isPressed("pause")) { (this->isPaused) ? this->pause(false) : this->pause(true); return; } else if (InputManager::isPressed('\n') || InputManager::isPressed(KEY_ENTER)) { if (! this->isPaused) { this->pause(true); return; // This needs to be here otherwise // ENTER goes to the menu and immediately // unpauses the game. } } else if (InputManager::isPressed("help")) { // Toggling Pause and Help window if (this->isPaused) { this->showHelp = false; this->timer.unpause(); this->timerSnake.unpause(); this->timerBoard.unpause(); } else { this->showHelp = true; this->timer.pause(); this->timerSnake.pause(); this->timerBoard.pause(); } } // Other keys are not used when paused. if (this->isPaused || this->showHelp) { this->pauseMenu->handleInput(); return; } if (InputManager::isPressed("left")) { this->player->move(Player::LEFT); } else if (InputManager::isPressed("right")) { this->player->move(Player::RIGHT); } else if (InputManager::isPressed("up")) { this->player->move(Player::UP); } else if (InputManager::isPressed("down")) { this->player->move(Player::DOWN); } } void Game::update() { if (this->gameOver) return; // If we're paused, only handle the menu. if (this->isPaused) { if (this->pauseMenu->willQuit()) { int option = this->pauseMenu->currentID(); switch(option) { case RESUME: this->pause(false); break; case RESTART: this->start(Globals::Game::current_level); return; case QUIT_MENU: this->userAskedToGoToMenu = true; break; case QUIT_GAME: this->userAskedToQuit = true; break; } this->pauseMenu->reset(); } return; } // Forcing Snake to move if enough time has passed // (time based on current level) this->timerSnake.pause(); int delta = this->getDelay(this->currentScore->speed); if (this->timerSnake.delta_ms() >= delta) { // Checking if on the previous frame // the Snake died. if (! this->player->isAlive()) { this->gameOver = true; // Check the return value and warns the player // if he just beat the high score this->scores->handle(this->currentScore); } else { // Actually move the player this->player->update(this->board); while (this->fruits->eatenFruit(this->player)) { this->player->increase(); // Score formula is kinda random and // scattered all over this file. // TODO: Center it all on the Score class. this->currentScore->points += this->currentScore->speed * 2; } this->fruits->update(this->player, this->board); } this->timerSnake.start(); } else this->timerSnake.unpause(); // Hey, can we scroll the Board? // If yes, on which direction should we do it? this->timerBoard.pause(); delta = Globals::Game::board_scroll_delay; if (this->timerBoard.delta_ms() >= delta) { if (Globals::Game::board_scroll_up) this->board->scrollUp(); if (Globals::Game::board_scroll_down) this->board->scrollDown(); if (Globals::Game::board_scroll_left) this->board->scrollLeft(); if (Globals::Game::board_scroll_right) this->board->scrollRight(); this->timerBoard.start(); } else this->timerBoard.unpause(); } void Game::draw() { this->layout->draw(this->pauseMenu); } bool Game::isOver() { return (this->gameOver); } bool Game::willQuit() { return this->userAskedToQuit; } bool Game::willReturnToMenu() { return this->userAskedToGoToMenu; } int Game::getDelay(int speed) { // returning delay in milliseconds if (speed < 1) return 800; switch (speed) { case 1: return 800; case 2: return 600; case 3: return 500; case 4: return 300; case 5: return 200; case 6: return 150; case 7: return 125; case 8: return 100; case 9: return 80; case 10: return 50; } return 50; } void Game::pause(bool option) { if (option) { if (this->isPaused) return; this->isPaused = true; this->showPauseMenu = true; this->timer.pause(); this->timerSnake.pause(); } else { if (! this->isPaused) return; this->isPaused = false; this->showPauseMenu = false; this->timer.unpause(); this->timerSnake.unpause(); } } nSnake-3.0.1/src/Game/Game.hpp000066400000000000000000000043551236634675000157770ustar00rootroot00000000000000#ifndef GAME_H_DEFINED #define GAME_H_DEFINED #include #include #include #include #include #include #include // Pre-defining it's layout to avoid circular dependency. class LayoutGame; class Game { friend class LayoutGame; public: Game(); virtual ~Game(); /// Starts game, optionally loading a level. /// /// @note This is not the path to the level file! /// It's merely a level name. /// /// @see BoardParser::load /// /// @note If no level name is specified, will default /// to an empty box. /// void start(std::string levelName=""); void handleInput(); void update(); void draw(); bool isOver(); /// If we'll quit the game right away. bool willQuit(); /// If we'll return to the main menu. bool willReturnToMenu(); /// Returns how much time (millisseconds) we need to wait /// for a specific #speed. int getDelay(int speed); void pause(bool option); // GameStateGame needs them to be public /// All the current level's score. /// It allows to read all the scores on this level, /// independent of game settings. ScoreFile* scores; /// Current score for this level. /// /// It shall get increased with time and in the end /// we'll test to see if it can enter this level's /// high score list. ScoreEntry* currentScore; protected: LayoutGame* layout; /// If the game is over (board is full of blocks). bool gameOver; bool userAskedToQuit; bool userAskedToGoToMenu; /// Timer that tells when to move the player, based /// on the current speed). Timer timerSnake; /// Timer that tells when to scroll the board, if /// this game setting is activated. Timer timerBoard; // Times how long the user have been playing the game. Timer timer; /// If the game is paused. /// May show other Windows while paused. bool isPaused; /// If it's showing the pause menu. /// Goes together with #isPaused. bool showPauseMenu; /// If it's showing the help screen. /// Goes together with #isPaused. bool showHelp; /// Menu that's shown only when the user presses Pause. Menu* pauseMenu; Player* player; Board* board; FruitManager* fruits; }; #endif //GAME_H_DEFINED nSnake-3.0.1/src/Game/Player.cpp000066400000000000000000000070101236634675000163440ustar00rootroot00000000000000#include #include Player::Player(int x, int y): alive(true), currentDirection(Player::RIGHT), nextDirection(Player::RIGHT) { // The Snake's head this->body.push_back(Body(x, y)); // Initializing the snake with 2 body pieces // (plus the head) this->body.push_back(Body(x - 1, y)); this->body.push_back(Body(x - 1, y)); } bool Player::isAlive() { return (this->alive); } int Player::getSize() { return (int)(this->body.size()); } int Player::getX() { return (this->body.front().x); } int Player::getY() { return (this->body.front().y); } void Player::moveTo(int x, int y) { this->body.front().x = x; this->body.front().y = y; } void Player::move(Direction dir) { this->nextDirection = dir; } void Player::kill() { this->alive = false; } void Player::update(Board* board) { // We have to make sure the snake doesn't do strange // things, like turning around on itself. switch(this->nextDirection) { case Player::RIGHT: if (this->currentDirection != Player::LEFT) this->currentDirection = this->nextDirection; break; case Player::LEFT: if (this->currentDirection != Player::RIGHT) this->currentDirection = this->nextDirection; break; case Player::UP: if (this->currentDirection != Player::DOWN) this->currentDirection = this->nextDirection; break; case Player::DOWN: if (this->currentDirection != Player::UP) this->currentDirection = this->nextDirection; break; }; // Making the rest of the body catch up for (unsigned int i = (this->body.size() - 1); i > 0; i--) { this->body[i].x = this->body[i - 1].x; this->body[i].y = this->body[i - 1].y; } // Moving the head switch(this->currentDirection) { case Player::RIGHT: this->body.front().x++; break; case Player::LEFT: this->body.front().x--; break; case Player::UP: this->body.front().y--; break; case Player::DOWN: this->body.front().y++; break; } // Checking if the player hit the board's extremes. // // @warning `teleport` may change player's X and Y! if (board->isBorder(this->body.front().x, this->body.front().y)) { if (board->style == Board::TELEPORT) board->teleport(this); else this->kill(); } int headx = this->body.front().x; int heady = this->body.front().y; // Checking if the head hits the body if (this->bodyHit(headx, heady, true)) this->kill(); // Checking for collisions on the board if (board->isWall(headx, heady)) this->kill(); } void Player::draw(Window* win) { // The body for (unsigned int i = 1; i < (this->body.size()); i++) win->printChar('o', this->body[i].x, this->body[i].y, Colors::pair(COLOR_GREEN, COLOR_DEFAULT, true)); // The head win->printChar(((this->alive) ? '@' : 'X'), this->body.front().x, this->body.front().y, Colors::pair(((this->alive) ? COLOR_GREEN : COLOR_RED), COLOR_DEFAULT, true)); } bool Player::headHit(int x, int y) { return ((this->body.front().x == x) && (this->body.front().y == y)); } bool Player::bodyHit(int x, int y, bool isCheckingHead) { int initial = 0; if (isCheckingHead) initial = 3; for (unsigned int i = initial; i < (this->body.size()); i++) if ((this->body[i].x == x) && (this->body[i].y == y)) return true; return false; } void Player::increase() { int lastx = this->body.back().x; int lasty = this->body.back().y; this->body.push_back(Body(lastx, lasty)); } nSnake-3.0.1/src/Game/Player.hpp000066400000000000000000000020511236634675000163510ustar00rootroot00000000000000#ifndef PLAYER_H_DEFINED #define PLAYER_H_DEFINED #include #include struct Body { int x; int y; Body(int x, int y): x(x), y(y) { } }; // Avoiding circular #include hell. class Board; /// class Player { public: enum Direction { UP, DOWN, LEFT, RIGHT }; Player(int x, int y); virtual ~Player() { }; bool isAlive(); int getSize(); int getX(); ///< Returns the head's x position. int getY(); ///< Returns the head's y position. void moveTo(int x, int y); void move(Direction direction); void kill(); void update(Board* board); void draw(Window* win); bool headHit(int x, int y); /// Tells if something at #x and #y collides with /// any part of the snake. /// /// @note #isHead is a huge HACK to allow me to /// check if the head collides with the body. /// Ignore it. bool bodyHit(int x, int y, bool isCheckingHead=false); void increase(); private: std::vector body; bool alive; Direction currentDirection; Direction nextDirection; }; #endif //PLAYER_H_DEFINED nSnake-3.0.1/src/Game/ScoreFile.cpp000066400000000000000000000221001236634675000167600ustar00rootroot00000000000000#include #include #include #include #include // getenv() #include // ofstream // HACK This will be initialized at `Globals::init()` std::string ScoreFile::directory = ""; std::string ScoreFile::extension = "nsnakescores"; ScoreEntry::ScoreEntry(): points(0), speed(0), level(""), fruits(0), random_walls(false), teleport(false), board_size(Globals::Game::LARGE), board_scroll_delay(0), board_scroll_left(false), board_scroll_right(false), board_scroll_up(false), board_scroll_down(false) { } bool ScoreEntry::isLike(ScoreEntry& other) { if (this->level != other.level) return false; // First thing we gotta check is if this score // was made on Arcade Mode (level is empty) // // If that's the case, then we care for the // board size if (this->level.empty()) { return (this->fruits == other.fruits && this->random_walls == other.random_walls && this->teleport == other.teleport && this->speed == other.speed && this->board_scroll_delay == other.board_scroll_delay && this->board_scroll_left == other.board_scroll_left && this->board_scroll_right == other.board_scroll_right && this->board_scroll_up == other.board_scroll_up && this->board_scroll_down == other.board_scroll_down && this->board_size == other.board_size); } // If not, the board size is not important, since levels // can have any size. return (this->fruits == other.fruits && this->random_walls == other.random_walls && this->teleport == other.teleport && this->speed == other.speed && this->board_scroll_delay == other.board_scroll_delay && this->board_scroll_left == other.board_scroll_left && this->board_scroll_right == other.board_scroll_right && this->board_scroll_up == other.board_scroll_up && this->board_scroll_down == other.board_scroll_down); } void ScoreFile::eraseAll() { // 1. Delete the arcade score fileerase this one // 2. Lists all files under the score dir and erase // the ones ending with a score extension Utils::File::rm_f(Globals::Config::scoresFile); std::vector files = Utils::File::ls(ScoreFile::directory); for (size_t i = 0; i < files.size(); i++) if (Utils::File::extension(files[i]) == ScoreFile::extension) Utils::File::rm_f(files[i]); } ScoreFile::ScoreFile(std::string levelName): highScore(NULL), level_name(levelName) { } void ScoreFile::load() { // Make it point nowhere, since we're refreshing // the score entries. this->highScore = NULL; // Score files are dependent of the level name. std::string score_file = (ScoreFile::directory + this->level_name + "." + ScoreFile::extension); // Will fall back to default high score file // (Arcade Mode) if no level was specified if (this->level_name.empty()) score_file = Globals::Config::scoresFile; if (! Utils::File::exists(score_file)) throw ScoreFileException("File '" + score_file + "' doesn't exist"); // Reading whole file's contents into a buffer std::ifstream file; file.open(score_file.c_str()); std::stringstream buffer; buffer << file.rdbuf(); file.close(); std::stringstream contents; contents << Utils::Base64::decode(buffer.str()); // Parsing file's contents as INI INI::Parser ini(contents); // If it's a score file from a different major version, // how should we react? // No need to worry about minor versions. std::string version = ini["version"]; if (version[0] != Globals::version[MAJOR]) { // Compare versions, lower, higher, whatever... Globals::Error::old_version_score_file = true; throw ScoreFileException("File '" + score_file + "' has an old version format"); } // Going through each group on the INI file // (each score the user had) for (INI::Level::Sections::const_iterator it = ini.top().ordered_sections.begin(); it != ini.top().ordered_sections.end(); ++it) { // This is SOO ugly! // We should NOT have to worry about INI parser's internals! INI::Level ini_score = (*it)->second; ScoreEntry entry; entry.level = ini_score["level"]; entry.points = Utils::String::to(ini_score["points"]); entry.speed = Utils::String::to(ini_score["speed"]); entry.fruits = Utils::String::to(ini_score["fruits"]); entry.random_walls = Utils::String::to(ini_score["random_walls"]); entry.teleport = Utils::String::to(ini_score["teleport"]); entry.board_scroll_delay = Utils::String::to(ini_score["board_scroll_delay"]); entry.board_scroll_left = Utils::String::to(ini_score["board_scroll_left"]); entry.board_scroll_right = Utils::String::to(ini_score["board_scroll_right"]); entry.board_scroll_up = Utils::String::to(ini_score["board_scroll_up"]); entry.board_scroll_down = Utils::String::to(ini_score["board_scroll_down"]); int board_size = Utils::String::to(ini_score["board_size"]); entry.board_size = Globals::Game::intToBoardSize(board_size); this->entries.push_back(entry); } // Finally, we have to pick the highest score // according to these game settings. ScoreEntry tmp_score; tmp_score.level = this->level_name; tmp_score.speed = Globals::Game::starting_speed; tmp_score.fruits = Globals::Game::fruits_at_once; tmp_score.random_walls = Globals::Game::random_walls; tmp_score.teleport = Globals::Game::teleport; tmp_score.board_size = Globals::Game::board_size; tmp_score.board_scroll_delay = Globals::Game::board_scroll_delay; tmp_score.board_scroll_left = Globals::Game::board_scroll_left; tmp_score.board_scroll_right = Globals::Game::board_scroll_right; tmp_score.board_scroll_up = Globals::Game::board_scroll_up; tmp_score.board_scroll_down = Globals::Game::board_scroll_down; for (size_t i = 0; i < (this->entries.size()); i++) { if (tmp_score.isLike(this->entries[i])) { this->highScore = &(this->entries[i]); break; } } if (this->highScore == NULL) { this->entries.push_back(tmp_score); this->highScore = &(this->entries[this->entries.size() - 1]); } } void ScoreFile::save() { // Score files are dependent of the level name. std::string score_file = (ScoreFile::directory + this->level_name + "." + ScoreFile::extension); // Will fall back to default high score file // if no level was specified if (this->level_name.empty()) score_file = Globals::Config::scoresFile; // Tries to create file if it doesn't exist. // If we can't create it at all let's just give up. if (! Utils::File::exists(score_file)) { Utils::File::create(score_file); if (! Utils::File::exists(score_file)) throw ScoreFileException("Could not create file '" + score_file + "'"); } // We'll recreate the whole score file from scratch INI::Parser ini; ini.create(); ini.top().addKey("version", std::string(VERSION)); // Adding each score entry on the file for (size_t i = 0; i < (this->entries.size()); i++) { std::string score_name = "score" + Utils::String::toString(i); ini.top().addGroup(score_name); ini(score_name).addKey("level", this->entries[i].level); ini(score_name).addKey("points", Utils::String::toString(this->entries[i].points)); ini(score_name).addKey("speed", Utils::String::toString(this->entries[i].speed)); ini(score_name).addKey("fruits", Utils::String::toString(this->entries[i].fruits)); ini(score_name).addKey("random_walls", Utils::String::toString(this->entries[i].random_walls)); ini(score_name).addKey("teleport", Utils::String::toString(this->entries[i].teleport)); int board_size = Globals::Game::boardSizeToInt(this->entries[i].board_size); ini(score_name).addKey("board_size", Utils::String::toString(board_size)); ini(score_name).addKey("board_scroll_delay", Utils::String::toString(this->entries[i].board_scroll_delay)); ini(score_name).addKey("board_scroll_left", Utils::String::toString(this->entries[i].board_scroll_left)); ini(score_name).addKey("board_scroll_right", Utils::String::toString(this->entries[i].board_scroll_right)); ini(score_name).addKey("board_scroll_up", Utils::String::toString(this->entries[i].board_scroll_up)); ini(score_name).addKey("board_scroll_down", Utils::String::toString(this->entries[i].board_scroll_down)); } std::stringstream contents; ini.dump(contents); std::ofstream file; file.open(score_file.c_str()); file << Utils::Base64::encode(contents.str()); file.close(); } bool ScoreFile::handle(ScoreEntry* score) { // No high score until now, we've made it! if (! this->highScore) { this->entries.push_back(*score); this->highScore = &(this->entries[this->entries.size() - 1]); return true; } // Wrong game settings? if (! score->isLike(*this->highScore)) return false; if ((score->points) > (this->highScore->points)) { this->highScore->points = score->points; return true; } return false; } nSnake-3.0.1/src/Game/ScoreFile.hpp000066400000000000000000000074761236634675000170100ustar00rootroot00000000000000#ifndef SCORE_H_DEFINED #define SCORE_H_DEFINED #include #include #include #include /// Custom exception class to specify an error that /// occurred during a level loading. /// class ScoreFileException : public std::exception { public: ScoreFileException(std::string message): message(message) { } ~ScoreFileException() throw() { } std::string message; }; /// A single entry on the high-score file. /// struct ScoreEntry { /// How many points the user got. unsigned int points; /// Under which game speed the score was made. unsigned int speed; /// On which level the user made this score. /// It defaults to "", which is the Arcade Mode. std::string level; /// How many fruits at once were allowed on this level. int fruits; /// If random walls were spawned on this level. bool random_walls; /// If teleport was enabled on this level. bool teleport; /// How large was the game board on this score. /// /// @note This is only valid on the Arcade Mode. /// Levels don't care about it. Globals::Game::BoardSize board_size; int board_scroll_delay; bool board_scroll_left; bool board_scroll_right; bool board_scroll_up; bool board_scroll_down; /// Creates an empty score entry. /// Since everything is public, access them /// without thinking twice. ScoreEntry(); /// Tells if both scores were made on exact same game settings. /// /// Scores are incompatible if they doesn't have the same /// game settings. /// It's unfair for a score with less fruits to be compared /// with one where more fruits were enabled. /// bool isLike(ScoreEntry& other); }; /// Stores points the player made on the game. /// /// There's different high scores for different /// game settings. /// /// Each time the user runs the game with a different /// configuration of the tweaks above, a new high /// score is generated. /// /// It always starts with 0 and if the player /// surpasses it, will be the new maximum. /// class ScoreFile { public: /// Default directory where we store the game score files. /// /// By default is the same directory as the level files /// (see BoardParser). /// /// So the default behavior is to create a score file with /// the same name as the level and a custom extension. static std::string directory; /// Default extension to save the score files. /// /// It's all the part that comes _after the dot_ on /// a file name. /// /// @note Defaults to "nsnakescores" static std::string extension; /// Erases all high score files. /// /// By "all" I mean every single score file from the arcade /// mode to all levels. /// static void eraseAll(); /// Creates a new score handler for the level #levelName. /// /// @note It doesn't actually work unless you call #load, mate ScoreFile(std::string levelName); /// Loads all high score entries based on a level name. /// /// The file where we save the high scores will be based /// on the level name. /// /// @note If `levelName` is empty, will fall back to /// the default score file (see Globals). /// void load(); /// Saves all the current scores on the file. void save(); /// Checks if #score is the highest score and /// make it so. /// /// Call this when you finished the game. /// /// @return If #score just became the highest score. bool handle(ScoreEntry* score); /// Maximum high score obtained for the current game. /// /// It will point to an element within #entries that /// has the highest score for this game settings. /// /// @note If this is NULL then we couldn't open the /// high score files! /// ScoreEntry* highScore; private: /// Name of the level under these scores fall into. /// Defaults to "" for Arcade Mode. std::string level_name; /// All score entries for this file std::vector entries; }; #endif //SCORE_H_DEFINED nSnake-3.0.1/src/Interface/000077500000000000000000000000001236634675000154355ustar00rootroot00000000000000nSnake-3.0.1/src/Interface/Animation/000077500000000000000000000000001236634675000173545ustar00rootroot00000000000000nSnake-3.0.1/src/Interface/Animation/Animation.hpp000066400000000000000000000010501236634675000220000ustar00rootroot00000000000000#ifndef ANIMATION_H_DEFINED #define ANIMATION_H_DEFINED #include /// Abstract interface to any kind of Animation. class Animation { public: /// Creates an Animation that will occur on #window. Animation(Window* window): window(window) { }; virtual ~Animation() {}; /// Loads all internal things. virtual void load() = 0; /// Updates Animation's internal state. virtual void update() = 0; /// Shows Animation on the screen. virtual void draw() = 0; protected: Window* window; }; #endif //ANIMATION_H_DEFINED nSnake-3.0.1/src/Interface/Animation/AnimationSnakes.cpp000066400000000000000000000035201236634675000231440ustar00rootroot00000000000000#include #include #include AnimationSnakes::AnimationSnakes(Window* window): Animation(window) { } void AnimationSnakes::load() { this->addSnake(); updateTimer.start(); addTimer.start(); } void AnimationSnakes::update() { // Adding yet another snake int delay = Utils::Random::between(1, 3) * 100; if ((addTimer.delta_ms() > delay) && (this->lilsnakes.size() < MAX_SNAKES)) { this->addSnake(); // Random add burst! if (Utils::Random::booleanWithChance(0.2501)) { for (int i = 0; i < Utils::Random::between(3, 5); i++) this->addSnake(); } addTimer.start(); } // Updating all snakes // They all drop once and get deleted as soon as they // leave the Window. if (updateTimer.delta_ms() > 50) { std::vector::iterator it = this->lilsnakes.begin(); while (it != this->lilsnakes.end()) { if (((*it).y - (*it).size) > (this->window->getH() - 1)) { it = this->lilsnakes.erase(it); } else { (*it).y++; ++it; } } updateTimer.start(); } } void AnimationSnakes::draw() { for (unsigned int i = 0; i < (this->lilsnakes.size()); i++) { window->printChar('@', this->lilsnakes[i].x, this->lilsnakes[i].y, Colors::pair(COLOR_GREEN, COLOR_DEFAULT, true)); for (int j = 1; j < (this->lilsnakes[i].size); j++) { window->printChar('o', this->lilsnakes[i].x, this->lilsnakes[i].y - j, Colors::pair(COLOR_GREEN, COLOR_DEFAULT)); } } } void AnimationSnakes::addSnake() { int newx = Utils::Random::between(1, this->window->getW() - 1); int newy = Utils::Random::between(0, 3); int size = Utils::Random::between(2, 14); this->lilsnakes.push_back(LilSnake(newx, newy, size)); } nSnake-3.0.1/src/Interface/Animation/AnimationSnakes.hpp000066400000000000000000000013161236634675000231520ustar00rootroot00000000000000#ifndef ANIMATIONSNAKES_H_DEFINED #define ANIMATIONSNAKES_H_DEFINED #include #include #include #define MAX_SNAKES 100 struct LilSnake { int x; int y; int size; LilSnake(int x, int y, int size): x(x), y(y), size(size) { } }; /// Rules and behavior of the Fire animation. class AnimationSnakes: public Animation { public: AnimationSnakes(Window* window); virtual ~AnimationSnakes() {}; void load(); void update(); void draw(); private: std::vector lilsnakes; /// Timer to update all snakes. Timer updateTimer; /// Timer to add another snake. Timer addTimer; void addSnake(); }; #endif //ANIMATIONSNAKES_H_DEFINED nSnake-3.0.1/src/Interface/Colors.cpp000066400000000000000000000106161236634675000174060ustar00rootroot00000000000000#include #include // strtol() bool Colors::hasColors = false; bool Colors::init() { if (has_colors() != TRUE) /* ncurses BOOL */ { Colors::hasColors = false; return false; } Colors::hasColors = true; start_color(); // This is a big hack to initialize all 64 // possible color pairs in ncurses. // // The thing is, all colors are between // COLOR_BLACK and COLOR_WHITE. // Since I've set a large number of enums covering // all possibilities, I can do it all in a for loop. // Check 'man init_pair' for more details. // // Here's the internal value of colors, // taken straight from : // // #define COLOR_BLACK 0 // #define COLOR_RED 1 // #define COLOR_GREEN 2 // #define COLOR_YELLOW 3 // #define COLOR_BLUE 4 // #define COLOR_MAGENTA 5 // #define COLOR_CYAN 6 // #define COLOR_WHITE 7 // int i, j, k = 1; for (i = COLOR_BLACK; i <= COLOR_WHITE; i++) { for (j = COLOR_BLACK; j <= COLOR_WHITE; j++) { init_pair(k, i, j); k++; } } // Besides the normal color pairs, we can use // whatever colors the user has currently set to // their terminal. // It looks more "natural". // // So COLOR_PAIR(-1, -1) is the default foreground // and background. // // Let's do it if the current terminal supports it. if (use_default_colors() != ERR) { // default background init_pair(64, COLOR_BLACK, COLOR_DEFAULT); init_pair(65, COLOR_RED, COLOR_DEFAULT); init_pair(66, COLOR_GREEN, COLOR_DEFAULT); init_pair(67, COLOR_YELLOW, COLOR_DEFAULT); init_pair(68, COLOR_BLUE, COLOR_DEFAULT); init_pair(69, COLOR_MAGENTA, COLOR_DEFAULT); init_pair(70, COLOR_CYAN, COLOR_DEFAULT); init_pair(71, COLOR_WHITE, COLOR_DEFAULT); } return true; } Color Colors::rgb(short r, short g, short b) { if (can_change_color() == FALSE) return 0; if (COLORS < 256) return 0; static int color_no = 8; color_no++; if (color_no >= COLORS) color_no = 8; // init_color receives values from 0 to 1000 int expand = 1000/255; init_color((color_no - 1), r*expand, g*expand, b*expand); return (color_no - 1); } Color Colors::hex(std::string hex) { if (hex[0] != '#') return 0; // sorry if (hex.size() != 7) return 0; // #RRGGBB format char col[3]; col[2] = '\0'; col[0] = hex[1]; col[1] = hex[2]; long r = strtol(col, NULL, 16); col[0] = hex[3]; col[1] = hex[4]; long g = strtol(col, NULL, 16); col[0] = hex[5]; col[1] = hex[6]; long b = strtol(col, NULL, 16); return Colors::rgb(r, g, b); } ColorPair Colors::pair(Color foreground, Color background, bool is_bold) { // Basic nCurses colors if ((foreground < 8) && (background < 8)) { if (background == COLOR_DEFAULT) { if (is_bold) return COLOR_PAIR(64 + foreground) | A_BOLD; else return COLOR_PAIR(64 + foreground); } if (is_bold) return COLOR_PAIR(foreground*8 + background + 1) | A_BOLD; else return COLOR_PAIR(foreground*8 + background + 1); } if (COLORS < 256) { if (is_bold) return COLOR_PAIR(0) | A_BOLD; else return COLOR_PAIR(0); } // Will create color pair // (above the 64 regular ones plus 12 default = 72) static int color_pair_no = 72; color_pair_no++; if (color_pair_no >= COLOR_PAIRS) color_pair_no = 72; init_pair((color_pair_no - 1), foreground, background); if (is_bold) return COLOR_PAIR(color_pair_no - 1) | A_BOLD; else return COLOR_PAIR(color_pair_no - 1); } Color Colors::fromString(std::string str) { if (str.empty()) return 255; if (str == "default") return COLOR_DEFAULT; if (str == "black") return COLOR_BLACK; if (str == "red") return COLOR_RED; if (str == "green") return COLOR_GREEN; if (str == "yellow") return COLOR_YELLOW; if (str == "blue") return COLOR_BLUE; if (str == "magenta") return COLOR_MAGENTA; if (str == "cyan") return COLOR_CYAN; if (str == "white") return COLOR_WHITE; // keep in mind this error code return 255; } ColorPair Colors::pairFromString(std::string foreground, std::string background, bool is_bold) { if (foreground.empty() || background.empty()) return 255; short f = Colors::fromString(foreground); short b = Colors::fromString(background); return Colors::pair(f, b, is_bold); } void Colors::activate(WINDOW* window, Color foreground, Color background) { Colors::pairActivate(window, Colors::pair(foreground, background)); } void Colors::pairActivate(WINDOW* window, ColorPair color) { wattrset(window, color); } nSnake-3.0.1/src/Interface/Colors.hpp000066400000000000000000000012331236634675000174060ustar00rootroot00000000000000#ifndef COLORS_H_DEFINED #define COLORS_H_DEFINED #include #include typedef short Color; typedef chtype ColorPair; #define COLOR_DEFAULT -1 /// namespace Colors { bool init(); Color rgb(short r, short g, short b); Color hex(std::string hex); ColorPair pair(Color foreground, Color background, bool is_bold=false); Color fromString(std::string str); ColorPair pairFromString(std::string foreground, std::string background, bool is_bold=false); void activate(WINDOW* window, Color foreground, Color background); void pairActivate(WINDOW* window, ColorPair color); // Var extern bool hasColors; }; #endif //COLORS_H_DEFINED nSnake-3.0.1/src/Interface/Dialog.cpp000066400000000000000000000063451236634675000173500ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include void Dialog::show(std::string message, bool pressAnyKey) { std::vector message_lines = Utils::String::split(message, '\n'); // The dialog needs to wrap around this text. So we need... int message_width = 0; // ...the char count of the widest line and... int message_height = 0; // ...the number of lines of the whole message message_height = message_lines.size(); for (size_t i = 0; i < message_lines.size(); i++) message_width = std::max(message_width, (int)message_lines[i].size()); // Now, to the size and position of the actual Dialog. // Making it centered on the screen int window_x = Layout::screenWidth /2 - (message_width + 2)/2; int window_y = Layout::screenHeight/2 - (message_height) /2; int window_width = message_width + 2; // left/right borders int window_height = message_height + 2; // top/bottom borders Window dialog(window_x, window_y, window_width, window_height); if (Globals::Screen::show_borders) { dialog.borders(Globals::Screen::fancy_borders ? Window::BORDER_FANCY : Window::BORDER_REGULAR); } // Before showing anything on the screen we must // call `refresh()`, to... well, refresh the // main screen buffer refresh(); // Show all lines, starting from (1, 1) for (size_t i = 0; i < message_lines.size(); i++) dialog.print(message_lines[i], 1, i + 1); dialog.refresh(); refresh(); // Wait forever to get any key... if (pressAnyKey) Ncurses::getInput(-1); } bool Dialog::askBool(std::string question, std::string title, bool default_value) { int windowx = Layout::screenWidth/2 - (question.size() + 12)/2; int windowy = Layout::screenHeight/2 - 5/2; Window dialog(windowx, windowy, question.size() + 2 + 10, // borders + empty space 5); if (Globals::Screen::show_borders) { dialog.borders(Globals::Screen::fancy_borders ? Window::BORDER_FANCY : Window::BORDER_REGULAR); } if (! title.empty()) dialog.setTitle(title); dialog.refresh(); Menu menu(1, 2, question.size() + 10, 2); std::vector options; options.push_back("Yes"); options.push_back("No"); MenuItemTextlist* list = new MenuItemTextlist(question, 0, options, (default_value ? "Yes" : "No")); menu.add(list); while (true) { // Drawing things dialog.clear(); menu.draw(&dialog); dialog.refresh(); refresh(); // Getting input (waiting infinitely for it) InputManager::update(-1); if (InputManager::isPressed("quit")) // user-defined return false; menu.handleInput(); if (InputManager::isPressed('\n') || InputManager::isPressed(KEY_ENTER)) { std::string str(menu.getString(0)); return (str == "Yes"); } } // Will never get here return false; } nSnake-3.0.1/src/Interface/Dialog.hpp000066400000000000000000000015341236634675000173500ustar00rootroot00000000000000#ifndef DIALOG_H_DEFINED #define DIALOG_H_DEFINED #include /// Quick-and-dirty functions to show GUI-like dialogs /// on the screen. /// namespace Dialog { /// Shows a message on the screen. /// Spawns a screen-centered dialog that respects multi-line /// strings, wrapping it's size around them. /// /// @note The multi-line delimiter is `\n` /// /// @param message Which string to show. /// @param pressAnyKey If it should wait for pressing a single key /// void show(std::string message, bool pressAnyKey=false); /// Spawns a Dialog box asking for a yes-or-no #question. /// Dialog optionally has a #title and a #default_value. /// /// @note If the user presses 'q' to quit, /// it'll return false. /// bool askBool(std::string question, std::string title="", bool default_value=false); }; #endif //DIALOG_H_DEFINED nSnake-3.0.1/src/Interface/Layout.cpp000066400000000000000000000035201236634675000174160ustar00rootroot00000000000000#include #include #include #include #include #include int Layout::screenWidth = 0; int Layout::screenHeight = 0; static int intendedWidth; static int intendedHeight; Layout::Layout(int width, int height): main(NULL) { intendedWidth = width; intendedHeight = height; } Layout::~Layout() { } void Layout::windowsInit() { clear(); // Gets the current width and height of the whole terminal. int current_height, current_width; getmaxyx(stdscr, current_height, current_width); if ((current_width < intendedWidth) || (current_height < intendedHeight)) { Ncurses::exit(); std::cerr << "Error! Your console screen is smaller than" << intendedWidth << "x" << intendedHeight << "\n" << "Please resize your window and try again" << std::endl; exit(EXIT_FAILURE); } // Sets global info Layout::screenWidth = current_width; Layout::screenHeight = current_height; // Creating the main window for this layout. // We'll center based on user's settings int main_x = 0; int main_y = 0; if (Globals::Screen::center_horizontally) main_x = current_width/2 - intendedWidth/2; if (Globals::Screen::center_vertically) main_y = current_height/2 - intendedHeight/2; this->main = new Window(main_x, main_y, intendedWidth, intendedHeight); if ((Globals::Screen::outer_border) && (Globals::Screen::show_borders)) { this->main->borders(Globals::Screen::fancy_borders ? Window::BORDER_FANCY : Window::BORDER_REGULAR); } this->main->refresh(); } void Layout::windowsExit() { SAFE_DELETE(this->main); } void Layout::draw() { // When subclassing, make sure to implement this! } nSnake-3.0.1/src/Interface/Layout.hpp000066400000000000000000000014211236634675000174210ustar00rootroot00000000000000#ifndef LAYOUT_H_DEFINED #define LAYOUT_H_DEFINED #include /// Interface for how the things are shown on the screen. /// /// Any Layout will have lots if Windows, each with a specific /// function: to show the game board, display high scores, /// show next pieces, and such. /// /// I suggest you subclass this and implement your layout /// elsewhere. /// class Layout { public: /// Full width of the terminal right now. static int screenWidth; /// Full height of the terminal right now. static int screenHeight; Layout(int width, int height); virtual ~Layout(); virtual void windowsInit(); virtual void windowsExit(); virtual void draw(); /// Layout's main Window, where all the others are inside. Window* main; }; #endif //LAYOUT_H_DEFINED nSnake-3.0.1/src/Interface/LayoutFirstTime.cpp000066400000000000000000000006701236634675000212500ustar00rootroot00000000000000#include #include #include LayoutFirstTime::LayoutFirstTime(int width, int height): Layout(width, height) { this->windowsInit(); } LayoutFirstTime::~LayoutFirstTime() { this->windowsExit(); } void LayoutFirstTime::windowsInit() { Layout::windowsInit(); } void LayoutFirstTime::windowsExit() { clear(); Layout::windowsExit(); } void LayoutFirstTime::draw() { } nSnake-3.0.1/src/Interface/LayoutFirstTime.hpp000066400000000000000000000007241236634675000212550ustar00rootroot00000000000000#ifndef LAYOUTFIRSTTIME_H_DEFINED #define LAYOUTFIRSTTIME_H_DEFINED #include #include #include /// How we show the screen at GameStateFirstTime. /// See it's documentation there. class LayoutFirstTime: public Layout { public: LayoutFirstTime(int width, int height); virtual ~LayoutFirstTime(); void windowsInit(); void windowsExit(); void draw(); }; #endif //LAYOUTFIRSTTIME_H_DEFINED nSnake-3.0.1/src/Interface/LayoutGame.cpp000066400000000000000000000124041236634675000202110ustar00rootroot00000000000000#include #include #include LayoutGame::LayoutGame(Game* game, int width, int height): Layout(width, height), game(game), gamewin(NULL), info(NULL), pause(NULL), help(NULL), boardwin(NULL), helpWindows(NULL) { this->windowsInit(); } LayoutGame::~LayoutGame() { this->windowsExit(); } void LayoutGame::windowsInit() { Layout::windowsInit(); this->main->setTitle("nsnake " VERSION); if (this->game->currentScore->level.empty()) this->main->setTitle("Arcade Mode", Window::TOP_RIGHT); else this->main->setTitle("Level " + this->game->board->getMetadata("name"), Window::TOP_RIGHT); // Leftmost window this->gamewin = new Window(this->main, WINDOW_FILL, WINDOW_FILL, WINDOW_FILL, this->main->getH() - 3); this->info = new Window(this->main, WINDOW_FILL, this->main->getH() - 2, WINDOW_FILL, 1); // Le pause window. this->pause = new Window(this->main, this->main->getW() / 4, this->main->getH() / 2 - 1, //center this->main->getW() / 2, 7); if (Globals::Screen::show_borders) { this->pause->borders(Globals::Screen::fancy_borders ? Window::BORDER_FANCY : Window::BORDER_REGULAR); } this->pause->setTitle("Paused"); // Le help window. this->help = new Window(this->main, this->main->getW() / 4, this->main->getH() / 4, this->main->getW() / 2, this->main->getH() / 2); if (Globals::Screen::show_borders) { this->help->borders(Globals::Screen::fancy_borders ? Window::BORDER_FANCY : Window::BORDER_REGULAR); } this->help->setTitle("Help"); this->helpWindows = new WindowGameHelp(); } void LayoutGame::windowsExit() { SAFE_DELETE(this->gamewin); SAFE_DELETE(this->info); SAFE_DELETE(this->pause); SAFE_DELETE(this->help); SAFE_DELETE(this->boardwin); SAFE_DELETE(this->helpWindows); this->main->clear(); // clear() as in Window this->main->refresh(); // clear() as in Window Layout::windowsExit(); } void LayoutGame::draw(Menu* menu) { if (! this->game) return; // This hack allows the game board to be centered if (! this->boardwin) { // initializing for the first time int x = this->gamewin->getW()/2 - this->game->board->getW()/2; int y = this->gamewin->getH()/2 - this->game->board->getH()/2; int w = this->game->board->getW(); int h = this->game->board->getH(); boardwin = new Window(this->gamewin, x, y, w, h); } this->main->clear(); // Will only show the requested windows then exit. if (this->game->isPaused && this->game->showPauseMenu) { this->pause->clear(); menu->draw(this->pause); this->pause->refresh(); refresh(); return; } if (this->game->showHelp) { this->helpWindows->run(); this->game->showHelp = false; // NCURSES NEEDS THIS refresh(); return; } // Statistics // A mess of direct Ncurses calls - fix this later this->info->clear(); this->info->print("a", 0, 0, 0); ColorPair hilite = Globals::Theme::hilite_text; this->info->print("Hi-Score", 0, 0, hilite); this->info->print("Score", this->info->getW()/3, 0, hilite); this->info->print("Speed", this->info->getW()/3 * 2, 0, hilite); // Default color wattrset(this->info->win, COLOR_PAIR(0)); if (this->game->scores->highScore) mvwprintw(this->info->win, 0, 9, "%u", this->game->scores->highScore->points); mvwprintw(this->info->win, 0, this->info->getW()/3 + 6, "%u", this->game->currentScore->points); mvwprintw(this->info->win, 0, this->info->getW()/3 * 2 + 6, "%d", this->game->currentScore->speed); // // Timer - how much time has passed since game start // this->rightmost->print("Timer", 2, 10, hilite); // long delta_s = this->game->timer.delta_s(); // int seconds = delta_s % 60; // int minutes = (delta_s / 60) % 60; // int hours = ((delta_s / 60) / 60) % 24; // wattrset(this->rightmost->win, COLOR_PAIR(0)); // mvwprintw(this->rightmost->win, 10, 8, "%02d:%02d:%02d", hours, minutes, seconds); // // Delay // this->rightmost->print("Delay", 2, 12, hilite); // wattrset(this->rightmost->win, COLOR_PAIR(0)); // mvwprintw(this->rightmost->win, 12, 8, "%dms", this->game->getDelay(this->game->score->level)); // // Bottom line - version and Help // this->rightmost->print("nsnake v" VERSION, 1, this->rightmost->getH() - 2, Colors::pair(COLOR_CYAN, COLOR_DEFAULT)); // this->rightmost->print("H", this->rightmost->getW() - 5, this->rightmost->getH() - 2, Colors::pair(COLOR_YELLOW, COLOR_DEFAULT)); // this->rightmost->print("elp", this->rightmost->getW() - 4, this->rightmost->getH() - 2, Colors::pair(COLOR_CYAN, COLOR_DEFAULT)); // this->rightmost->refresh(); // } // Board and main game stuff this->gamewin->clear(); this->game->board->draw(boardwin); this->game->player->draw(boardwin); this->game->fruits->draw(boardwin); this->gamewin->refresh(); this->info->refresh(); this->main->refresh(); // NCURSES NEEDS THIS refresh(); } nSnake-3.0.1/src/Interface/LayoutGame.hpp000066400000000000000000000017661236634675000202270ustar00rootroot00000000000000#ifndef LAYOUTGAMEMODESURVIVAL_H_DEFINED #define LAYOUTGAMEMODESURVIVAL_H_DEFINED #include #include #include #include #include class LayoutGame: public Layout { public: // Telling the compiler (clang) that we're // hiding the parent's virtual function using Layout::draw; LayoutGame(Game* game, int width, int height); virtual ~LayoutGame(); void windowsInit(); void windowsExit(); void draw(Menu* menu); private: Game* game; // On `Layout` we have a `main` Window, where // everything's inside /// Where the game is shown. Window* gamewin; /// Where we display game stats (points, etc) Window* info; // HACK so we can get width and height to create menu // on the Game public: /// Contains the pause menu. Window* pause; /// Contains the help screen. Window* help; private: Window* boardwin; WindowGameHelp* helpWindows; }; #endif //LAYOUTGAMEMODESURVIVAL_H_DEFINED nSnake-3.0.1/src/Interface/LayoutMainMenu.cpp000066400000000000000000000054541236634675000210600ustar00rootroot00000000000000#include #include #include #include #include #include #include #include LayoutMainMenu::LayoutMainMenu(int width, int height, GameStateMainMenu* state): Layout(width, height), state(state), logo(NULL), menu(NULL), animationwin(NULL), animation(NULL) { this->windowsInit(); } LayoutMainMenu::~LayoutMainMenu() { this->windowsExit(); } void LayoutMainMenu::windowsInit() { Layout::windowsInit(); // LOGO this->logo = new Window(this->main, 0, 0, 56, 7); // MENU this->menu = new Window(this->main, 55, 0, 24, WINDOW_FILL); this->menu->setTitle("Main Menu"); if (Globals::Screen::show_borders) { this->menu->borders(Globals::Screen::fancy_borders ? Window::BORDER_FANCY : Window::BORDER_REGULAR); } this->menu->refresh(); // ANIMATION this->animationwin = new Window(this->main, 0, this->logo->getH(), this->logo->getW(), this->main->getH() - this->logo->getH() - 1); this->animation = new AnimationSnakes(this->animationwin); this->animation->load(); } void LayoutMainMenu::windowsExit() { SAFE_DELETE(this->menu); SAFE_DELETE(this->logo); SAFE_DELETE(this->animationwin); SAFE_DELETE(this->animation); Layout::windowsExit(); } void LayoutMainMenu::draw(Menu* menu) { this->animation->update(); this->main->clear(); this->animation->draw(); this->logo->clear(); this->logo->print(Utils::String::split(" __ _ _______ __ _ _______ ___ _ _______\n" "| | | || || | | || _ || | | || |\n" "| |_| || _____|| |_| || |_| || |_| || ___|\n" "| || |_____ | || || _|| |___ \n" "| _ ||_____ || _ || || |_ | ___|\n" "| | | | _____| || | | || _ || _ || |___ \n" "|_| |__||_______||_| |__||__| |__||___| |_||_______|", '\n'), 0, 0, Colors::pair(COLOR_GREEN, COLOR_DEFAULT, true)); this->logo->refresh(); // Yay! this->menu->clear(); menu->draw(this->menu); this->menu->refresh(); this->main->refresh(); // NCURSES NEEDS THIS refresh(); } nSnake-3.0.1/src/Interface/LayoutMainMenu.hpp000066400000000000000000000024041236634675000210550ustar00rootroot00000000000000#ifndef LAYOUTMAINMENU_H_DEFINED #define LAYOUTMAINMENU_H_DEFINED #include #include #include #include // circular dependence on GameStateMainMenu class GameStateMainMenu; /// How we show the screen at GameStateMainMenu. /// /// This class is merely a wrapper over functions that /// print GameStateMainMenu on the screen. /// /// Thus, it needs full access to it's internal values. /// So it comes down to this circular dependency. /// class LayoutMainMenu: public Layout { public: // Telling the compiler (clang) that we're // hiding the parent's virtual function using Layout::draw; LayoutMainMenu(int width, int height, GameStateMainMenu* state); virtual ~LayoutMainMenu(); void windowsInit(); void windowsExit(); /// Shows the Main Menu screen, along with drawing #menu. void draw(Menu* menu); /// Redraws everything from scratch, refreshing borders /// and stuff. void redraw(); /// We need this so we can access the states' flags /// and decide best on how to print it. GameStateMainMenu* state; Window* logo; Window* menu; Window* animationwin; /// Cure thing at the main menu. Animation* animation; }; #endif //LAYOUTMAINMENU_H_DEFINED nSnake-3.0.1/src/Interface/Menu/000077500000000000000000000000001236634675000163415ustar00rootroot00000000000000nSnake-3.0.1/src/Interface/Menu/Menu.cpp000066400000000000000000000216751236634675000177640ustar00rootroot00000000000000#include #include #include #include Menu::Menu(int x, int y, int width, int height): current(NULL), currentIndex(0), x(x), y(y), width(width), height(height), selected(false), selectedItem(NULL) { } Menu::~Menu() { for (unsigned int i = 0; i < (this->item.size()); i++) SAFE_DELETE(this->item[i]); this->item.clear(); } void Menu::add(MenuItem* item) { this->item.push_back(item); // Means we're the first item being added! if (this->item.size() == 1) { this->current = this->item.back(); this->currentIndex = this->item.size() - 1; } // Means we're the first non-nil item being added! unsigned int i = 0; for (i = 0; i < (this->item.size()); i++) if (this->item[i]) break; this->current = this->item[i]; this->currentIndex = i; } void Menu::addBlank() { this->item.push_back(NULL); } void Menu::removeByID(int id) { std::vector::iterator it = this->item.begin(); while (it != this->item.end()) { if (!(*it)) ++it; if ((*it)->id == id) { if (this->current == *it) { this->goNext(); this->currentIndex--; } this->item.erase(it); return; } else ++it; } } void Menu::removeByLabel(std::string label) { std::vector::iterator it = this->item.begin(); while (it != this->item.end()) { if (!(*it)) ++it; if ((*it)->label == label) { if (this->current == *it) { this->goNext(); this->currentIndex--; } this->item.erase(it); return; } else ++it; } } void Menu::draw(Window* window) { // If we have more Items than we can draw, we need to // ask the user to scroll. // So these are the indexes of the items we'll actually // show on the screen. unsigned int draw_begin = 0; unsigned int draw_end = this->item.size(); if (this->height < (int)this->item.size()) { if ((int)this->currentIndex <= this->height/2) { draw_end = this->height - 1; } else if ((int)this->currentIndex < ((int)this->item.size() - this->height/2) - 1) { draw_begin = this->currentIndex - this->height/2; draw_end = this->currentIndex + this->height/2; } else { draw_begin = this->item.size() - this->height; } } // `i` is the index of the item to draw for (unsigned int curitem = draw_begin, yoffset = 0; curitem < draw_end; curitem++, yoffset++) { // Menu is big and needs scrolling, // we need to show (more) on the top if ((curitem == draw_begin) && (curitem != 0)) { window->print("(more)", this->x + this->width/2 - 3, this->y + yoffset, Colors::pair(COLOR_WHITE, COLOR_DEFAULT)); continue; } // Menu is big and needs scrolling, // we need to show (more) on the end if ((curitem == draw_end - 1) && (curitem != this->item.size() - 1)) { window->print("(more)", this->x + this->width/2 - 3, this->y + yoffset + 1, Colors::pair(COLOR_WHITE, COLOR_DEFAULT)); continue; } // // THIS IS VERY UGLY // HOW CAN I LAY DOWN A CLASS HIERARCHY // AND OVERRIDE PARENT FUNCTIONS ON CHILD CLASSES // IF WHEN I HAVE A PARENT POINTER I CANT LET THE // COMPILER DECIDE WETHER TO CALL PARENT OR CHILD // FUNCTIONS? // // MenuItemCheckbox* pCheckbox = dynamic_casta // Blank Menu Item, will show horizontal line if (! this->item[curitem]) { for (int j = 0; j < (this->width); j++) window->printChar(((Globals::Screen::fancy_borders) ? ACS_HLINE : '-'), this->x + j, this->y + yoffset, Colors::pair(COLOR_WHITE, COLOR_DEFAULT)); } else { this->item[curitem]->draw(window, this->x, this->y + yoffset, this->width, // Highlighting current item if // it's the current. (this->item[curitem] == this->current)); } } } void Menu::handleInput() { if (InputManager::noKeyPressed()) return; if (InputManager::isPressed("down") || // user-defined InputManager::isPressed(KEY_DOWN) || InputManager::isPressed('\t')) this->goNext(); else if (InputManager::isPressed("up") || InputManager::isPressed(KEY_UP) || InputManager::isPressed(KEY_BTAB)) this->goPrevious(); else if (InputManager::isPressed(KEY_HOME) || InputManager::isPressed(KEY_PPAGE)) this->goFirst(); else if (InputManager::isPressed(KEY_END) || InputManager::isPressed(KEY_NPAGE)) this->goLast(); else if (InputManager::isPressed(KEY_ENTER) || InputManager::isPressed('\n')) { // Will only quit if the selected item is a simple // item or label - more complex ones doesn't quit. if (this->current->type == MenuItem::ITEM || this->current->type == MenuItem::LABEL) { this->selected = true; this->selectedItem = this->current; } else this->current->handleInput(); } else { if (this->current) this->current->handleInput(); } } void Menu::goNext() { // Empty element, piece of cake if (this->item.size() == 0) return; // Empty element, also piece of cake if (this->item.size() == 1) { this->current = *(this->item.begin()); this->currentIndex = 0; return; } // Last element... // Well, if the last element is nil then we have // a problem. if (this->current == this->item.back()) { // Assuming we're not nil, things will go smooth if (this->currentIndex == (this->item.size() - 1)) { this->goFirst(); return; } } this->currentIndex++; this->current = this->item[this->currentIndex]; if (! this->current) this->goNext(); } void Menu::goPrevious() { if (this->item.size() == 0) return; if (this->item.size() == 1) { this->current = this->item.front(); this->currentIndex = 0; return; } if (this->current == this->item.front()) { if (this->currentIndex == 0) { this->goLast(); return; } } this->currentIndex--; this->current = this->item[this->currentIndex]; if (! this->current) this->goPrevious(); } void Menu::goFirst() { if (this->item.size() == 0) return; if (this->item.size() == 1) { this->current = this->item.front(); this->currentIndex = 0; return; } this->current = this->item.front(); this->currentIndex = 0; if (! this->current) this->goNext(); } void Menu::goLast() { if (this->item.size() == 0) return; if (this->item.size() == 1) { this->current = this->item.front(); this->currentIndex = 0; return; } this->current = this->item.back(); this->currentIndex = (this->item.size() - 1); if (! this->current) this->goPrevious(); } void Menu::goRandom() { if (this->item.size() == 0) return; this->currentIndex = Utils::Random::between(0, this->item.size() - 1); this->current = this->item[this->currentIndex]; } bool Menu::willQuit() { // Will only quit if the user selected an item // and the item selected is valid. return (this->selected && this->selectedItem); } std::string Menu::currentLabel() { if (! this->current) this->goNext(); return (this->current->label); } int Menu::currentID() { if (! this->current) this->goNext(); return (this->current->id); } bool Menu::getBool(int id) { for (unsigned int i = 0; i < (this->item.size()); i++) { if (! this->item[i]) continue; if (this->item[i]->id == id) { // Either the type got messed up or we have // two items with the same id. if (this->item[i]->type != MenuItem::CHECKBOX) return false; // This cast may be dangerous if the type was // somehow changed. MenuItemCheckbox* c = (MenuItemCheckbox*)this->item[i]; return c->isChecked(); } } return false; } int Menu::getInt(int id) { for (unsigned int i = 0; i < (this->item.size()); i++) { if (! this->item[i]) continue; if (this->item[i]->id == id) { // Either the type got messed up or we have // two items with the same id. if (this->item[i]->type != MenuItem::NUMBERBOX) return -1; // This cast may be dangerous if the type was // somehow changed. MenuItemNumberbox* c = (MenuItemNumberbox*)this->item[i]; return c->current; } } return -1; } std::string Menu::getString(int id) { for (unsigned int i = 0; i < (this->item.size()); i++) { if (! this->item[i]) continue; if (this->item[i]->id == id) { // Either the type got messed up or we have // two items with the same id. if (this->item[i]->type == MenuItem::TEXTBOX) { MenuItemTextbox* c = (MenuItemTextbox*)this->item[i]; return c->currentText; } else if (this->item[i]->type == MenuItem::TEXTLIST) { MenuItemTextlist* c = (MenuItemTextlist*)this->item[i]; return c->currentText(); } else return ""; // This cast may be dangerous if the type was // somehow changed. } } return ""; } void Menu::reset() { this->selected = false; this->selectedItem = NULL; } nSnake-3.0.1/src/Interface/Menu/Menu.hpp000066400000000000000000000070111236634675000177550ustar00rootroot00000000000000#ifndef MENU_H_DEFINED #define MENU_H_DEFINED #include #include #include #include #include #include #include #include #include /// List of selectable items. /// /// @warning When adding items, make sure they're allocated /// dynamically! I mean POINTERS, damn it. /// Otherwise SEGFAULTS when trying to delete /// static stuff. /// /// @warning If you create a menu full of null elements /// (with #addBlank()), it'll probably get into /// an infinite loop when you call #goNext() /// or #goPrevious. /// I still have to find a way to fix this. /// class Menu { public: /// Creates a menu at #x and #y with #width and #height. /// /// It isn't attached to any specific Window, /// except when you call Menu::draw(). Menu(int x, int y, int width, int height); virtual ~Menu(); void add(MenuItem* item); void addBlank(); /// Removes the menu item with #id. void removeByID(int id); /// Removes the menu item with #label. /// /// @note It'll delete the first entry found with #label. /// /// @warning This is far more unreliable than /// #removeById(), provided you have unique IDs. /// void removeByLabel(std::string label); /// Draws the whole Menu on #window. void draw(Window* window); /// Makes the menu react to input, /// as seen on the global InputManager. void handleInput(); /// Makes the menu select the next item. /// @note This is a recursive function to guarantee we'll /// always point to a non-nil element. void goNext(); /// Makes the menu select the previous item. /// @note This is a recursive function to guarantee we'll /// always point to a non-nil element. void goPrevious(); void goFirst(); void goLast(); void goRandom(); /// Tells if the user selected an item that quits the menu. bool willQuit(); /// Returns the label of the currently selected item. std::string currentLabel(); /// Returns the user-specified id of the selected item. int currentID(); /// Returns the bool internal value of item that has #id. /// @note Meaning the item must be a MenuItemCheckbox. bool getBool(int id); /// Returns the integer value of the item that has #id. /// @note Meaning the item must be a MenuItemNumberbox. int getInt(int id); /// Returns the string value of the item that has #id. /// @note Meaning the item must be a MenuItemTextbox or /// MenuItemTextlist. std::string getString(int id); /// Makes the menu able to be selected again. /// /// When the user selects an item, #willQuit() returns /// true. /// By calling this function, you make possible to select /// things again. void reset(); // FIXME: create an iterator so I dont need to expose // this vector /// Container of all the options inside the menu. std::vector item; /// Current item selected. /// @note It's public because we might want to change /// current item's attributes. Don't mess with /// the pointer itself! MenuItem* current; protected: /// Index of the currently selected item. unsigned int currentIndex; int x; int y; int width; int height; /// Tells if the user selected an item (pressed Enter). bool selected; /// Specifies which item the user pressed Enter on. MenuItem* selectedItem; }; #endif //MENU_H_DEFINED nSnake-3.0.1/src/Interface/Menu/MenuAlphabetic.cpp000066400000000000000000000044711236634675000217340ustar00rootroot00000000000000#include #include #include #include MenuAlphabetic::MenuAlphabetic(int x, int y, int width, int height): Menu(x, y, width, height) { } // Local function that compares two Menu Items bool menuItemLess(const MenuItem* x, const MenuItem* y) { return Utils::String::caseInsensitiveSmallerString(x->label, y->label); } void MenuAlphabetic::add(MenuItem* item) { Menu::add(item); // This is a very specific behavior, read with attention. // // If the menu has no blank items, // we're going to sort it normally. // // If it has a single blank item, we'll sort the entire menu // starting from the item AFTER the blank one. std::vector::iterator firstItem = this->item.begin(); // So here we look for a blank item for (size_t i = 0; i < (this->item.size()); i++) if (this->item[i] == NULL) { firstItem += i; // Pointing to the current items' position break; } // And start sorting from AFTER it std::sort(firstItem + 1, this->item.end(), menuItemLess); // TODO: make it keep pointing to the previous item // instead of jumping to the first this->goFirst(); } void MenuAlphabetic::handleInput() { Menu::handleInput(); // This is very ugly... char key = std::tolower(InputManager::pressedKey); if (key >= 'a' && key <= 'z') { // See specific behavior on `MenuAlphabetic::add` // // Start looking only after the first Blank item. int startingIndex = 0; for (size_t i = 0; i < this->item.size(); i++) if (this->item[i] == NULL) { startingIndex = i; break; } // And only make possible to jump on items AFTER // the blank one for (size_t i = startingIndex + 1; i < this->item.size(); i++) if (std::tolower(this->item[i]->label[0]) == key) { this->currentIndex = i; this->current = this->item[i]; break; } } } void MenuAlphabetic::goRandom() { // See specific behavior on `MenuAlphabetic::add` // // Start looking only after the first Blank item. int startingIndex = 0; for (size_t i = 0; i < this->item.size(); i++) if (this->item[i] == NULL) { startingIndex = i; break; } this->currentIndex = Utils::Random::between(startingIndex + 1, this->item.size() - 1); this->current = this->item[this->currentIndex]; } nSnake-3.0.1/src/Interface/Menu/MenuAlphabetic.hpp000066400000000000000000000011551236634675000217350ustar00rootroot00000000000000#ifndef MENUALPHABETIC_H_DEFINED #define MENUALPHABETIC_H_DEFINED #include /// Menu on which its items are always sorted alphabetically. /// /// It has special navigation keys: by pressing a letter /// it will jump straight to /// the first occurrence of it. class MenuAlphabetic: public Menu { public: MenuAlphabetic(int x, int y, int width, int height); void add(MenuItem* item); void handleInput(); /// Selects a random item right AFTER the first /// blank one. void goRandom(); }; #endif //MENUALPHABETIC_H_DEFINED nSnake-3.0.1/src/Interface/Menu/MenuItem.cpp000066400000000000000000000007661236634675000206010ustar00rootroot00000000000000#include #include #include #include MenuItem::MenuItem(std::string label, int id): type(MenuItem::ITEM), label(label), id(id) { } void MenuItem::draw(Window* window, int x, int y, int width, bool hilite) { window->print(this->label.substr(0, width), x, y, ((hilite) ? Globals::Theme::hilite_text: Globals::Theme::text)); } void MenuItem::handleInput() { } nSnake-3.0.1/src/Interface/Menu/MenuItem.hpp000066400000000000000000000027501236634675000206010ustar00rootroot00000000000000#ifndef MENUITEM_H_DEFINED #define MENUITEM_H_DEFINED #include #include /// Simplest type of item possible, with a label /// and user-defined id. /// struct MenuItem { /// All possible item types. /// When inheriting and creating your own, make sure to /// place it here. /// enum MenuItemType { ITEM, LABEL, CHECKBOX, NUMBERBOX, TEXTBOX, TEXTLIST }; /// Create a MenuItem, with user-defined #id. /// /// @note MenuItems on the same menu should have /// different #id s! If not, results are unpredictable. MenuItem(std::string label, int id); virtual ~MenuItem() { }; /// Shows this item at #x, #y with #width. /// /// If this is the current item, send #hilite as true. /// /// @note Yeah, height is always 1. virtual void draw(Window* window, int x, int y, int width, bool hilite=false); /// Makes the menu item react to input, /// as seen on the global InputManager. /// /// Each type of MenuItem might react to input differently. /// A text box might want to show printable characters, /// a check box might want to check if space bar was pressed, /// whatever. /// /// When inheriting this, make sure to implement it. virtual void handleInput(); /// Specific type of this widget. /// @note It is set on the Constructor, /// don't change it manually! MenuItemType type; /// Text that will be shown on the screen. std::string label; /// User-defined id to identify this item. int id; }; #endif //MENUITEM_H_DEFINED nSnake-3.0.1/src/Interface/Menu/MenuItemCheckbox.cpp000066400000000000000000000040541236634675000222420ustar00rootroot00000000000000#include #include #include MenuItemCheckbox::MenuItemCheckbox(std::string label, int id, bool initial): MenuItem(label, id), checked(initial) { this->type = MenuItem::CHECKBOX; // placing it above wont work } void MenuItemCheckbox::draw(Window* window, int x, int y, int width, bool hilite) { // Will draw // label text // If not hilite. // If hilite: // label < text > MenuItem::draw(window, x, y, width - 9, hilite); // button width int posx = x + width; window->print(((hilite)? "<": "["), posx - 8, y, ((hilite)? Globals::Theme::hilite_text: Globals::Theme::text)); window->print("ON", posx - 7, y, ((this->checked) ? Globals::Theme::hilite_text: Globals::Theme::text)); window->print("|", posx - 5, y, Globals::Theme::text); window->print("OFF", posx - 4, y, ((this->checked) ? Globals::Theme::text : Globals::Theme::hilite_text)); window->print(((hilite)? ">": "]"), posx - 1, y, ((hilite)? Globals::Theme::hilite_text: Globals::Theme::text)); } void MenuItemCheckbox::handleInput() { if (InputManager::noKeyPressed()) return; if (InputManager::isPressed("left") || // user-defined InputManager::isPressed(KEY_LEFT)) this->check(true); else if (InputManager::isPressed("right") || // user-defined InputManager::isPressed(KEY_RIGHT)) this->check(false); else if (InputManager::isPressed(' ') || InputManager::isPressed('\n') || InputManager::isPressed(KEY_ENTER)) this->toggle(); } void MenuItemCheckbox::check(bool option) { this->checked = option; } void MenuItemCheckbox::toggle() { this->checked = !(this->checked); } bool MenuItemCheckbox::isChecked() { return this->checked; } nSnake-3.0.1/src/Interface/Menu/MenuItemCheckbox.hpp000066400000000000000000000010561236634675000222460ustar00rootroot00000000000000#ifndef MENUITEMCHECKBOX_H_DEFINED #define MENUITEMCHECKBOX_H_DEFINED #include /// A little box that can be checked or not. /// Used to get a boolean from the user. /// struct MenuItemCheckbox: public MenuItem { MenuItemCheckbox(std::string label, int id, bool initial=false); virtual ~MenuItemCheckbox() {}; void draw(Window* window, int x, int y, int width, bool hilite=false); void handleInput(); void check(bool option); void toggle(); bool isChecked(); bool checked; }; #endif //MENUITEMCHECKBOX_H_DEFINED nSnake-3.0.1/src/Interface/Menu/MenuItemLabel.cpp000066400000000000000000000013731236634675000215340ustar00rootroot00000000000000#include #include MenuItemLabel::MenuItemLabel(std::string label, int id, std::string rightLabel): MenuItem(label, id), rightLabel(rightLabel) { this->type = MenuItem::LABEL; } void MenuItemLabel::draw(Window* window, int x, int y, int width, bool hilite) { unsigned int rightLabelSize = this->rightLabel.size(); MenuItem::draw(window, x, y, width - rightLabelSize - 1, hilite); window->print(this->rightLabel, x + width - rightLabelSize, y, ((hilite) ? Globals::Theme::hilite_text: Globals::Theme::text)); } void MenuItemLabel::handleInput() { } void MenuItemLabel::set(std::string str) { this->rightLabel = str; } nSnake-3.0.1/src/Interface/Menu/MenuItemLabel.hpp000066400000000000000000000007401236634675000215360ustar00rootroot00000000000000#ifndef MENUITEMLABEL_H_DEFINED #define MENUITEMLABEL_H_DEFINED #include /// Shows a Menu Item with left and right labels. struct MenuItemLabel: public MenuItem { MenuItemLabel(std::string label, int id, std::string rightLabel); virtual ~MenuItemLabel() {}; void draw(Window* window, int x, int y, int width, bool hilite=false); void handleInput(); void set(std::string str); std::string rightLabel; }; #endif //MENUITEMLABEL_H_DEFINED nSnake-3.0.1/src/Interface/Menu/MenuItemNumberbox.cpp000066400000000000000000000071631236634675000224610ustar00rootroot00000000000000#include #include #include #include #include MenuItemNumberbox::MenuItemNumberbox(std::string label, int id, int min, int max, int initial, int jump): MenuItem(label, id), min(min), max(max), initial(initial), current(initial), jump(jump) { this->type = MenuItem::NUMBERBOX; // placing it above wont work } void MenuItemNumberbox::draw(Window* window, int x, int y, int width, bool hilite) { std::string number = Utils::String::toString(this->current); // Will draw // label text // If not hilite. // If hilite: // label < text > MenuItem::draw(window, x, y, (width - number.size() - 5), hilite); int rightmost = x + width; window->print(((hilite)? "<": "["), rightmost - number.size() - 2, y, ((hilite)? Globals::Theme::hilite_text: Globals::Theme::text)); window->print(((hilite)? ">": "]"), rightmost - 1, y, ((hilite)? Globals::Theme::hilite_text: Globals::Theme::text)); window->print(number, rightmost - number.size() - 1, y, Globals::Theme::hilite_text); } void MenuItemNumberbox::handleInput() { if (InputManager::noKeyPressed()) return; // These will allow the user to type numbers // and set the current value. // It the user press numbers within a well-defined // time delta, they'll add up to the current value // until hit the maximum. // If the user doesn't press a number for that // period, the timer "resets". static Timer lastKeyTimer; static int lastKeyDelay = 500; static bool firstDigit = false; static bool secondDigit = false; static bool thirdDigit = false; int input = InputManager::pressedKey; // Special case, input was a number if (input >= '0' && input <= '9') { if (! firstDigit) { this->set(input - '0'); firstDigit = true; lastKeyTimer.start(); return; } if (lastKeyTimer.delta_ms() < lastKeyDelay) { if (! secondDigit) { this->set(this->current * 10 + (input - '0')); secondDigit = true; lastKeyTimer.start(); return; } if (! thirdDigit) { this->set(this->current * 10 + (input - '0')); thirdDigit = true; lastKeyTimer.start(); return; } } else { // reset everything this->set(input - '0'); firstDigit = true; secondDigit = false; thirdDigit = false; lastKeyTimer.start(); } return; } // Anything else if (InputManager::isPressed("left") || // user-defined InputManager::isPressed(KEY_LEFT)) this->decrease(); else if (InputManager::isPressed("right") || InputManager::isPressed(KEY_RIGHT)) this->increase(); else if (InputManager::isPressed('r') || InputManager::isPressed('R') || InputManager::isPressed(' ') || InputManager::isPressed('\n') || InputManager::isPressed(KEY_ENTER)) this->reset(); } void MenuItemNumberbox::set(int value) { this->current = value; this->cap(); } void MenuItemNumberbox::increase() { this->current += this->jump; this->cap(); } void MenuItemNumberbox::decrease() { this->current -= this->jump; this->cap(); } void MenuItemNumberbox::reset() { this->current = this->initial; } void MenuItemNumberbox::cap() { if (this->current > this->max) this->current = this->max; if (this->current < this->min) this->current = this->min; } nSnake-3.0.1/src/Interface/Menu/MenuItemNumberbox.hpp000066400000000000000000000026461236634675000224670ustar00rootroot00000000000000#ifndef MENUITEMNUMBERBOX_H_DEFINED #define MENUITEMNUMBERBOX_H_DEFINED #include /// Allows to select a number, kinda like a slider. /// /// There's a number and by pressing left and right you /// add or remove units of it. /// /// @note When you press a number, it will allow you to /// directly set it for a while. /// For example, pressing '1', '2' and '3' would /// set the number first to '1', then to '12' and /// then to '123'. /// struct MenuItemNumberbox: public MenuItem { /// Create a new number box. /// /// @param label Textual label on the left of the item /// @param id Unique identifier so the menu can know which item this is /// @param min Minimal allowed value for the number /// @param max Maximum allowed value for the number /// @param initial Initial value for the number /// @param jump How many units will the number jump when user press left or right /// MenuItemNumberbox(std::string label, int id, int min, int max, int initial, int jump=1); virtual ~MenuItemNumberbox() { }; void draw(Window* window, int x, int y, int width, bool hilite=false); void handleInput(); void increase(); void decrease(); void set(int value); void reset(); int min; int max; int initial; int current; int jump; private: /// Assures the #current value will remain within boundaries. void cap(); }; #endif //MENUITEMNUMBERBOX_H_DEFINED nSnake-3.0.1/src/Interface/Menu/MenuItemTextbox.cpp000066400000000000000000000030351236634675000221470ustar00rootroot00000000000000#include #include #include #include MenuItemTextbox::MenuItemTextbox(std::string label, int id, int width, std::string initial): MenuItem(label, id), currentText(initial), width(width) { } void MenuItemTextbox::draw(Window* window, int x, int y, int width, bool hilite) { // Drawing label before actual textbox MenuItem::draw(window, x, y, width - (this->width) - 1, hilite); // Maximum x id int posx = x + width; // Drawing the current text if ((int)(this->currentText.size()) > (this->width)) window->print(this->currentText.substr(this->width), (posx - this->width), y, Globals::Theme::textbox); else window->print(this->currentText, (posx - this->width), y, Globals::Theme::textbox); int spacex = (posx - this->width) - this->currentText.size(); // Drawing the rest of the spaces for (unsigned int i = 0; i < (this->width - this->currentText.size()); i++) window->printChar(' ', spacex + i, y, Globals::Theme::textbox); } // Local function that tells in #input can be interpreted // as a printable char (meaning no control character, // as seen on the ASCII table). bool isPrintable(int input) { return ((input > 32) && (input < 127)); } void MenuItemTextbox::handleInput() { if (InputManager::noKeyPressed()) return; if (isPrintable(InputManager::pressedKey)) { this->currentText += (char)(InputManager::pressedKey); return; } if (InputManager::isPressed(KEY_BACKSPACE)) Utils::String::pop_back(this->currentText); } nSnake-3.0.1/src/Interface/Menu/MenuItemTextbox.hpp000066400000000000000000000010101236634675000221430ustar00rootroot00000000000000#ifndef MENUITEMTEXTBOX_H_DEFINED #define MENUITEMTEXTBOX_H_DEFINED #include /// Place where you can input characters. /// /// For more info, check out MenuItem. struct MenuItemTextbox: public MenuItem { MenuItemTextbox(std::string label, int id, int width, std::string initial); virtual ~MenuItemTextbox() {}; void draw(Window* window, int x, int y, int width, bool hilite=false); void handleInput(); std::string currentText; int width; }; #endif //MENUITEMTEXTBOX_H_DEFINED nSnake-3.0.1/src/Interface/Menu/MenuItemTextlist.cpp000066400000000000000000000047361236634675000223430ustar00rootroot00000000000000#include #include #include #include #include MenuItemTextlist::MenuItemTextlist(std::string label, int id, std::vector options, std::string initial): MenuItem(label, id), options(options), currentIndex(-1) { this->type = MenuItem::TEXTLIST; for (unsigned int i = 0; i < options.size(); ++i) { if (options[i] == initial) { this->currentIndex = i; break; } } if ((this->currentIndex == -1) || (options.empty())) throw std::runtime_error("Invalid initial value for MenuItemTextlist"); } void MenuItemTextlist::draw(Window* window, int x, int y, int width, bool hilite) { std::string text = this->options[this->currentIndex]; // Will draw // label text // If not hilite. // If hilite: // label < text > MenuItem::draw(window, x, y, (width - text.size() - 5), hilite); int rightmost = x + width; window->print(((hilite)? "<": "["), rightmost - text.size() - 2, y, ((hilite)? Globals::Theme::hilite_text: Globals::Theme::text)); window->print(((hilite)? ">": "]"), rightmost - 1, y, ((hilite)? Globals::Theme::hilite_text: Globals::Theme::text)); window->print(text, rightmost - text.size() - 1, y, Globals::Theme::hilite_text); } void MenuItemTextlist::handleInput() { if (InputManager::noKeyPressed()) return; if (InputManager::isPressed("left") || // user-defined InputManager::isPressed(KEY_LEFT)) this->decrease(); else if (InputManager::isPressed("right") || InputManager::isPressed(KEY_RIGHT)) this->increase(); else if (InputManager::isPressed('r') || InputManager::isPressed('R') || InputManager::isPressed(' ')) this->reset(); } void MenuItemTextlist::increase() { this->currentIndex++; if (this->currentIndex >= (int)this->options.size()) this->currentIndex = 0; } void MenuItemTextlist::decrease() { this->currentIndex--; if (this->currentIndex < 0) this->currentIndex = (this->options.size() - 1); } void MenuItemTextlist::reset() { this->currentIndex = 0; } std::string MenuItemTextlist::currentText() { return this->options[this->currentIndex]; } nSnake-3.0.1/src/Interface/Menu/MenuItemTextlist.hpp000066400000000000000000000011761236634675000223430ustar00rootroot00000000000000#ifndef MENUITEMTEXTLIST_H_DEFINED #define MENUITEMTEXTLIST_H_DEFINED #include #include #include /// A list of selectable text class MenuItemTextlist: public MenuItem { public: MenuItemTextlist(std::string label, int id, std::vector options, std::string initial); virtual ~MenuItemTextlist() { }; void draw(Window* window, int x, int y, int width, bool hilite); void handleInput(); void increase(); void decrease(); void reset(); std::string currentText(); private: std::vector options; int currentIndex; }; #endif //MENUITEMTEXTLIST_H_DEFINED nSnake-3.0.1/src/Interface/Ncurses.cpp000066400000000000000000000030631236634675000175650ustar00rootroot00000000000000#include bool Ncurses::init() { initscr(); // TODO check for failing cbreak(); // Character input doesnt require the key anymore curs_set(0); // Makes the blinking cursor invisible noecho(); // Wont print the keys received through input nodelay(stdscr, TRUE); // Wont wait for input keypad(stdscr, TRUE); // Support for extra keys (life F1, F2, ... ) // Ncurses' global variable meaning number of milliseconds // to wait after the user presses ESC. // // VIM uses 25ms, so should you. // Source: http://en.chys.info/2009/09/esdelay-ncurses/ ESCDELAY = 25; refresh(); // Refresh the layout (prints whats in the layout bu return true; } void Ncurses::exit() { erase(); refresh(); endwin(); } void Ncurses::delay_ms(int delay) { napms(delay); } int Ncurses::getInput(int delay_ms) { // Will use select() function int retval = 0; int c = 0; fd_set input; struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = delay_ms * 1000; // microseconds // If #delay_ms is -1, we'll wait infinitely // (sending NULL to #select()) struct timeval* timeout_p = NULL; if (delay_ms != -1) timeout_p = &timeout; FD_ZERO(&input); FD_SET(STDIN_FILENO, &input); // This function is somewhat complex // check 'man select' for info retval = select(FD_SETSIZE, &input, NULL, NULL, timeout_p); // Ncurses' function that works without delay // (because we nodelay()'ed) c = getch(); if ((retval == 1) && (c == ERR)) // ERROR return -1; if (retval == 0) return ERR; //engine.input.none; return c; } nSnake-3.0.1/src/Interface/Ncurses.hpp000066400000000000000000000013761236634675000175770ustar00rootroot00000000000000#ifndef NCURSES_H_DEFINED #define NCURSES_H_DEFINED #include #include #include // select() #include // STDIN_FILENO /// Everything related to the terminal library Ncurses. namespace Ncurses { /// Initializes Ncurses mode. bool init(); /// Quits Ncurses mode. void exit(); /// Sleeps for #delay miliseconds. void delay_ms(int delay); /// Returns a pressed character within a /// timespan of #delay_ms (milliseconds). /// /// @note If you send -1, it'll block the execution, /// waiting for the input indefinitely. /// /// @note It's #int because nCurses uses some other /// values that don't fit on a #char variable. int getInput(int delay_ms=-1); }; #endif //NCURSES_H_DEFINED nSnake-3.0.1/src/Interface/Window.cpp000066400000000000000000000122371236634675000174150ustar00rootroot00000000000000#include #include // stringstream #include /* dag-nabbit, PDCurses (windows) doesnt have 'mvwhline' */ #if OS_IS_WINDOWS #define mvwhline my_mvwhline #endif /** * PDCurses (on Windows) doesn't have this function, so I need * to re-implement it. * * @todo implement more features - see 'man mvwhline' */ void my_mvwhline(WINDOW* win, int y, int x, chtype ch, int num) { int i; for (i = 0; i < num; i++) mvwaddch(win, y, (x + i), ch); } Window::Window(int x, int y, int w, int h): win(NULL), error(false), x(x), y(y), width(w), height(h), borderType(BORDER_NONE), topLeftTitle(""), topRightTitle(""), bottomLeftTitle(""), bottomRightTitle("") { this->win = newwin(height, width, y, x); if (!this->win) this->error = true; } Window::Window(Window* parent, int x, int y, int width, int height): win(NULL), error(false), borderType(BORDER_NONE), topLeftTitle(""), topRightTitle(""), bottomLeftTitle(""), bottomRightTitle("") { // By sending any parameter as 0, we want it to expand // until possible. // Let's expand based if the parent window has borders // or not. if (parent->borderType == BORDER_NONE) { if (width == 0) width = parent->width; if (height == 0) height = parent->height; } else { // Has borders if (x == 0) x = 1; if (y == 0) y = 1; if (width == 0) width = parent->width - 2; if (height == 0) height = parent->height - 2; } this->x = x; this->y = y; this->width = width; this->height = height; this->win = derwin(parent->win, height, width, y, x); if (!win) this->error = true; } Window::~Window() { if (this->win) delwin(this->win); } bool Window::isValid() { return !(this->error); } void Window::resize(int w, int h) { wresize(this->win, h, w); this->width = w; this->height = h; } void Window::print(std::string str, int x, int y, ColorPair pair) { Colors::pairActivate(this->win, pair); mvwaddstr(this->win, y, x, str.c_str()); } void Window::print(std::vector lines, int x, int y, ColorPair pair) { for (size_t i = 0; i < lines.size(); i++) this->print(lines[i], x, y + i, pair); } void Window::printChar(int c, int x, int y, ColorPair pair) { Colors::pairActivate(this->win, pair); mvwaddch(this->win, y, x, c); } void Window::setBackground(chtype ch, ColorPair pair) { wbkgd(this->win, ch | pair); } void Window::refresh() { //wrefresh(this->win); // I've changed all calls to wrefresh() to wnoutrefresh // because when I have several WINDOW*, it gets heavy // to do the former. // // As a tradeoff, I need to call `refresh()` at the end // of every draw cycle. // wnoutrefresh(this->win); } void Window::clear() { werase(this->win); // Redrawing borders if existing if (this->borderType != BORDER_NONE) this->borders(this->borderType); // Now, to the titles! if (! this->topLeftTitle.empty()) { this->print(this->topLeftTitle, 1, 0, Colors::pair(COLOR_BLUE, COLOR_DEFAULT)); } if (! this->bottomLeftTitle.empty()) { this->print(this->bottomLeftTitle, 0, this->getH() - 1, Colors::pair(COLOR_BLUE, COLOR_DEFAULT)); } if (! this->topRightTitle.empty()) { int x = (this->getW() - 1); int w = this->topRightTitle.size(); this->print(this->topRightTitle, x - w, 0, Colors::pair(COLOR_BLUE, COLOR_DEFAULT)); } if (! this->bottomRightTitle.empty()) { int x = (this->getW() - 1); int w = this->bottomRightTitle.size(); this->print(this->bottomRightTitle, x - w, this->getH() - 1, Colors::pair(COLOR_BLUE, COLOR_DEFAULT)); } } int Window::getW() const { return this->width; } int Window::getH() const { return this->height; } int Window::getX() const { return this->x; } int Window::getY() const { return this->y; } void Window::borders(BorderType type) { this->borderType = type; if (type == Window::BORDER_NONE) return; if (type == Window::BORDER_FANCY) { wborder(this->win, ACS_VLINE | Colors::pair(COLOR_WHITE, COLOR_DEFAULT), ACS_VLINE | Colors::pair(COLOR_BLACK, COLOR_DEFAULT, true), ACS_HLINE | Colors::pair(COLOR_WHITE, COLOR_DEFAULT), ACS_HLINE | Colors::pair(COLOR_BLACK, COLOR_DEFAULT, true), ACS_ULCORNER | Colors::pair(COLOR_WHITE, COLOR_DEFAULT, true), ACS_URCORNER | Colors::pair(COLOR_WHITE, COLOR_DEFAULT), ACS_LLCORNER | Colors::pair(COLOR_WHITE, COLOR_DEFAULT), ACS_LRCORNER | Colors::pair(COLOR_BLACK, COLOR_DEFAULT, true)); } else if (type == Window::BORDER_REGULAR) { wattrset(this->win, Colors::pair(COLOR_BLACK, COLOR_DEFAULT, true)); wborder(this->win, '|', '|', '-', '-', '+', '+', '+', '+'); } } void Window::horizontalLine(int x, int y, int c, int width, ColorPair pair) { Colors::pairActivate(this->win, pair); mvwhline(this->win, y, x, c, width); } void Window::setTitle(std::string title, WindowTitlePosition position) { switch (position) { case TOP_LEFT: this->topLeftTitle = title; break; case TOP_RIGHT: this->topRightTitle = title; break; case BOTTOM_LEFT: this->bottomLeftTitle = title; break; case BOTTOM_RIGHT: this->bottomRightTitle = title; break; default: return; } } nSnake-3.0.1/src/Interface/Window.hpp000066400000000000000000000045011236634675000174150ustar00rootroot00000000000000#ifndef WINDOW_H_DEFINED #define WINDOW_H_DEFINED #include #include #include #include /// Handy alias to make child Windows stretch their sizes to /// the maximum allowed by the parent. #define WINDOW_FILL 0 /// A segment of the terminal screen (2D char matrix). /// class Window { public: enum BorderType { BORDER_NONE, BORDER_REGULAR, BORDER_FANCY }; Window(int x, int y, int w, int h); Window(Window* parent, int x, int y, int width, int height); virtual ~Window(); virtual void resize(int w, int h); bool isValid(); /// Shows text #str at #x #y on the window with color #pair. /// /// @note It defaults to white text on black background. void print(std::string str, int x, int y, ColorPair pair=0); /// Shows multiple text lines #lines at #x #y on the window /// with color #pair. /// /// @note It also defaults to white text on black background. /// @note Use it together with `Utils::String::split`: /// /// window.print(Utils::String::split("multiple\nlines", '\n'), /// x, y, color_pair); /// void print(std::vector lines, int x, int y, ColorPair pair=0); /// Shows #c at #x #y with color #pair. /// /// @note It also defaults to white text on black background. void printChar(int c, int x, int y, ColorPair pair=0); void setBackground(chtype ch, ColorPair pair); void refresh(); void clear(); int getW() const; int getH() const; int getX() const; int getY() const; void borders(BorderType type); void horizontalLine(int x, int y, int c, int width, ColorPair pair); enum WindowTitlePosition { TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT }; /// Sets a text that will appear at the top of the Window. /// /// By default it's shown on top of the borders, with /// a highlighted color tone. /// void setTitle(std::string title, WindowTitlePosition position=Window::TOP_LEFT); /// Ncurses' internal data structure. /// IT'S PUBLIC BECAUSE SOMETIMES I NEED TO CALL DIRECT /// NCURSES CALLS - FIX THIS WINDOW* win; protected: /// Tells if we've got some initialization error. bool error; int x; int y; int width; int height; BorderType borderType; std::string topLeftTitle; std::string topRightTitle; std::string bottomLeftTitle; std::string bottomRightTitle; }; #endif //WINDOW_H_DEFINED nSnake-3.0.1/src/Interface/WindowGameHelp.cpp000066400000000000000000000142741236634675000210230ustar00rootroot00000000000000#include #include #include #include #include #include WindowGameHelp::WindowGameHelp() { int width = 40; int height = 17; int windowx = Layout::screenWidth/2 - width/2; int windowy = Layout::screenHeight/2 - height/2; this->main = new Window(windowx, windowy, width, height); if (Globals::Screen::show_borders) { this->main->borders(Globals::Screen::fancy_borders ? Window::BORDER_FANCY : Window::BORDER_REGULAR); } Window* win; // Help win = new Window(this->main, 0, 0, WINDOW_FILL, WINDOW_FILL); this->windows.push_back(win); win = new Window(this->main, 0, 0, WINDOW_FILL, WINDOW_FILL); this->windows.push_back(win); } void WindowGameHelp::run() { int activatedIndex = 0; while (true) { // Refreshing Windows this->main->clear(); this->windows[activatedIndex]->clear(); this->main->print(((activatedIndex == 0) ? "(Help)" : " Help "), 2, 0, ((activatedIndex == 0) ? Globals::Theme::textbox : Globals::Theme::hilite_text)); this->main->print(((activatedIndex == 1) ? "(Credits)" : " Credits "), 12, 0, ((activatedIndex == 1) ? Globals::Theme::textbox : Globals::Theme::hilite_text)); // HACK TO AVOID THE BORDERS FROM BEING // BOLD. GOTTA SOLVE THIS MISTERY. Colors::pairActivate(this->main->win, Globals::Theme::text); // Help Window if (activatedIndex == 0) { this->windows[0]->print("In-game controls:\n", 0, 0, Globals::Theme::hilite_text); this->windows[0]->print(Utils::String::split("Move up\n" "Move down\n" "Move left\n" "Move right\n" "Pause game\n" "Quit anytime\n" "Show help", '\n'), 1, 1, Globals::Theme::hilite_text); this->windows[0]->print(Utils::String::split(InputManager::keyToString(InputManager::getBind("up")) + "\n" + InputManager::keyToString(InputManager::getBind("down")) + "\n" + InputManager::keyToString(InputManager::getBind("left")) + "\n" + InputManager::keyToString(InputManager::getBind("right")) + "\n" + InputManager::keyToString(InputManager::getBind("pause")) + "\n" + InputManager::keyToString(InputManager::getBind("quit")) + "\n" + InputManager::keyToString(InputManager::getBind("help")), '\n'), 14, 1, Globals::Theme::text); this->windows[0]->print("Menu controls:\n", 0, 9, Globals::Theme::hilite_text); this->windows[0]->print(Utils::String::split("First item\n" "Last item", '\n'), 1, 10, Globals::Theme::hilite_text); this->windows[0]->print(Utils::String::split("page up\n" "page down", '\n'), 14, 10, Globals::Theme::text); this->windows[0]->print(Utils::String::split(" Settings and scores are stored at:\n" " `~/.local/share/nsnake/`", '\n'), 0, 13, Globals::Theme::text); } // // Credits else if (activatedIndex == 1) { this->windows[1]->print(Utils::String::split(" _ __ _ __ _ ____ \n" "| |\\ | ( (` | |\\ | / /\\ | |_/ | |_ \n" "|_| \\| _)_) |_| \\| /_/--\\ |_| \\ |_|__", '\n'), 0, 0, Colors::pair(COLOR_BLUE, COLOR_DEFAULT, true)); this->windows[1]->print(" v" VERSION " (built " DATE ")", 0, 3, Colors::pair(COLOR_GREEN, COLOR_DEFAULT, true)); this->windows[1]->print(Utils::String::split("Try `nsnake --help` and `man nsnake`\n" "\n" "Game made by Alexandre Dantas,\n" "contact him at \n" "Thanks for playing this game :)\n" "\n" "Homepage:\n" " http://nsnake.alexdantas.net/\n" "Source Code:\n" " https://github.com/alexdantas/nsnake/", '\n'), 0, 5, Globals::Theme::text); } this->windows[activatedIndex]->refresh(); this->main->refresh(); refresh(); // Getting Input InputManager::update(); if (InputManager::isPressed("left") || // user-defined InputManager::isPressed(KEY_LEFT)) { activatedIndex--; if (activatedIndex < 0) activatedIndex = 0; } else if (InputManager::isPressed("right") || // user-defined InputManager::isPressed(KEY_RIGHT)) { activatedIndex++; if (activatedIndex > 1) activatedIndex = 1; } else if (InputManager::isPressed("quit") || InputManager::isPressed(KEY_ENTER) || InputManager::isPressed('\n')) return; } } nSnake-3.0.1/src/Interface/WindowGameHelp.hpp000066400000000000000000000006361236634675000210250ustar00rootroot00000000000000#ifndef WINDOWGAMEHELP_H_DEFINED #define WINDOWGAMEHELP_H_DEFINED #include #include /// Specific Window that shows Help and other info /// during Game. /// class WindowGameHelp { public: WindowGameHelp(); virtual ~WindowGameHelp() {}; /// Updates and draws all tabs. void run(); private: Window* main; std::vector windows; }; #endif //WINDOWGAMEHELP_H_DEFINED nSnake-3.0.1/src/Misc/000077500000000000000000000000001236634675000144305ustar00rootroot00000000000000nSnake-3.0.1/src/Misc/Array2D.hpp000066400000000000000000000027721236634675000164150ustar00rootroot00000000000000#ifndef ARRAY2D_H_DEFINED #define ARRAY2D_H_DEFINED #include #include // size_t /// Two-dimensional array. /// /// This class tries to be as simple as C's matrix handling /// and very extensible, with `std::vector`. /// /// ## Note /// /// Array's contents must be a type/class that has a /// constructor with no arguments. /// /// So if you create your `MyClass` and want a 2D array of it, /// make sure `MyClass()` has a constructor that receive no /// arguments. /// template class Array2D { public: /// Creates a 2D array with `width` and `height`. Array2D(int width, int height); virtual ~Array2D() { }; /// Returns element at `x` `y`. T at(int x, int y) { return contents[x][y]; } void set(int x, int y, const T& value) { contents[x][y] = value; } /// Width size of the array. size_t width(); /// Height size of the array. size_t height(); private: /// Internal storage of elements. std::vector > contents; }; // Damn you templates! // // I need to leave the function definitions on the header // since we need to tell the compiler to create any possible // templates for each type called on the whole program. template Array2D::Array2D(int width, int height) { contents.resize(width); for (int i = 0; i < width; i++) contents[i].resize(height); } template size_t Array2D::width() { return contents.size(); } template size_t Array2D::height() { return contents[0].size(); } #endif //ARRAY2D_H_DEFINED nSnake-3.0.1/src/Misc/Timer.cpp000066400000000000000000000030041236634675000162110ustar00rootroot00000000000000#include #include // NULL // How many microseconds exists in a second #define MICRO_IN_SECONDS 1000000 // Local functino that returns current microsecond // amount since the Epoch. // static suseconds_t get_ticks() { struct timeval tmp; gettimeofday(&(tmp), NULL); return tmp.tv_usec + (tmp.tv_sec * MICRO_IN_SECONDS); } Timer::Timer(): startMark(0), stopMark(0), pausedMark(0), running(false), paused(false) {} void Timer::start() { this->startMark = get_ticks(); this->stopMark = 0; this->pausedMark = 0; this->running = true; this->paused = false; } void Timer::pause() { if (!running || paused) return; this->pausedMark = get_ticks() - (this->startMark); this->running = false; this->paused = true; } void Timer::unpause() { if (!paused || running) return; this->startMark = (get_ticks()) - (this->pausedMark); this->pausedMark = 0; this->running = true; this->paused = false; } bool Timer::isRunning() { return this->running; } bool Timer::isPaused() { return this->paused; } suseconds_t Timer::delta_us() { if (this->isRunning()) return get_ticks() - this->startMark; if (this->paused) return this->pausedMark; // Something went wrong here if (this->startMark == 0) return 0; return (this->pausedMark) - (this->startMark); } suseconds_t Timer::delta_ms() { return this->delta_us() / 1000; } suseconds_t Timer::delta_s() { return this->delta_ms() / 1000; } nSnake-3.0.1/src/Misc/Timer.hpp000066400000000000000000000016211236634675000162210ustar00rootroot00000000000000#ifndef TIMER_H_DEFINED #define TIMER_H_DEFINED #include /// class Timer { public: Timer(); /// Sets a starting point for the timer. /// If called multiple times, will restart. void start(); /// Temporarily stops the timer. void pause(); /// Restarts the timer if it was paused. void unpause(); /// Tells if the timer's still running (hasn't called stop()) bool isRunning(); /// Tells if the timer's paused. bool isPaused(); /// Returns the whole timer's difference in milisseconds. // @note If the timer's not started, will return 0. suseconds_t delta_us(); /// Returns the milisseconds part of the timer's difference. suseconds_t delta_ms(); /// Returns the seconds part of the timer's difference. suseconds_t delta_s(); protected: suseconds_t startMark; suseconds_t stopMark; suseconds_t pausedMark; bool running; bool paused; }; #endif /* TIMER_H_DEFINED */ nSnake-3.0.1/src/Misc/Utils.cpp000066400000000000000000000310561236634675000162410ustar00rootroot00000000000000// _________________________________________________________ // | _ __ ___ _ _ _ __ | // | \ \ / / /\ | |_) | |\ | | | | |\ | / /`_ | // | \_\/\/ /_/--\ |_| \ |_| \| |_| |_| \| \_\_/ | // | | // | Adventurers, beware... | // | | // | * The following file has lots of methods accumulated | // | over the years. | // | * There isn't too much cohesion between them, so try | // | to understand them individually. | // | | // | | // | * They're mostly poor-coded, sorry 'bout that. | // | | // `---------------------------------------------------------' #include #include // sstream #include // find_if #include // time() #include // usleep() #include // opendir(), readdir() #include // readdir() #include // ofstream #include // ofstream #include // system() #include #include // strchr() // C++11 compatibility // I wish I could use those: // #include // #include // ___ __ _ ___ ___ _ // | |_) / /\ | |\ | | | \ / / \ | |\/| // |_| \ /_/--\ |_| \| |_|_/ \_\_/ |_| | void Utils::Random::seed() { // Poor choice for random numbers, I know // I wish I could use C++11's random generators... srand(time(NULL)); } int Utils::Random::between(int min, int max) { if (min > max) std::swap(min, max); return (rand() % (max - min + 1) + min); } bool Utils::Random::boolean() { // If a random number between 0 and 9 is even int random_int = Utils::Random::between(0, 9); return ((random_int % 2) == 0); } bool Utils::Random::booleanWithChance(float percent) { int x = Utils::Random::between(0, 99); return (x < (percent * 100)); } // _____ _ _ ____ // | | | | | |\/| | |_ // |_| |_| |_| | |_|__ void Utils::Time::delay_ms(int delay) { usleep((useconds_t)delay * 100); } // ____ _ _ ____ // | |_ | | | | | |_ // |_| |_| |_|__ |_|__ bool Utils::File::exists(std::string path) { return (Utils::File::size(path) != -1); } off_t Utils::File::size(std::string path) { struct stat s; if (stat(path.c_str(), &s) < 0) return -1; return s.st_size; } void Utils::File::mkdir_p(std::string path) { std::string tmp(path); if (Utils::String::back(tmp) == '/') tmp[tmp.size() - 1] = '\0'; for (std::string::iterator p = tmp.begin(); (*p) != '\0'; p++) { if (*p == '/') { *p = '\0'; mkdir(tmp.c_str(), S_IRWXU); *p = '/'; } } mkdir(tmp.c_str(), S_IRWXU); } void Utils::File::rm_rf(std::string path) { if (! Utils::File::isDirectory(path)) return; // Another BIG UGY HACK // // Since my program's already non-portable // (POSIX systems only) and I don't have the // time nor means to either use Boost or // implement my own file functions, I'll // have to do a big hack. // // It's ugly, please ignore this function. // // I can't believe you're still reading. // Please don't continue from here. // // I've dishounored my family. // So ashamed of myself. // // // Next thing you know we're throwing gotos // everywhere. // // // // Still reading? // // ...OK, I've warned you std::string command("rm -rf " + path); system(command.c_str()); } void Utils::File::rm_f(std::string path) { if (Utils::File::isDirectory(path)) return; // This is ALSO another big hack. // God-dang it std::string command("rm -f " + path); system(command.c_str()); } bool Utils::File::create(std::string path) { FILE* fp = fopen(path.c_str(), "w"); if (! fp) return false; fclose(fp); return true; } void Utils::File::write(std::string path, std::string contents) { std::ofstream file; file.open(path.c_str()); // if it was C++11 we could've used std::string file << contents; } bool Utils::File::isDirectory(std::string path) { struct stat s; if (stat(path.c_str(), &s) < 0) return false; return ((S_ISDIR(s.st_mode))? true: false); } bool Utils::File::isFile(std::string path) { struct stat s; if (stat(path.c_str(), &s) < 0) return false; return ((S_ISREG(s.st_mode))? true: false); } std::vector Utils::File::ls(std::string path) { std::vector v; if (! Utils::File::isDirectory(path)) return v; // Opening directory DIR* dir; if (! (dir = opendir(path.c_str()))) return v; // Assuring 'path' ends with '/' if (Utils::String::back(path) != '/') path.push_back('/'); // Getting contents struct dirent* ent; while ((ent = readdir(dir))) { std::string s(path + ent->d_name); // Skipping obvious '.' and '..' dirs if ((s == (path + '.')) || (s == (path + ".."))) continue; v.push_back(s); } closedir(dir); return v; } std::string Utils::File::getHome() { if (! getenv("HOME")) return ""; std::string s(getenv("HOME")); if (Utils::String::back(s) != '/') s.push_back('/'); return s; } std::string Utils::File::getUser() { std::string s = Utils::File::getHome(); if (s.empty()) return ""; // Removing trailing '/' Utils::String::pop_back(&s); // Getting everything after other '/' size_t pos = s.rfind('/'); if (pos == std::string::npos) // woah, wtf return ""; return s.substr(pos + 1); } std::string Utils::File::basename(std::string path) { #if defined(_WIN32) && !defined(__CYGWIN__) char separator = '\\'; #else char separator = '/'; #endif size_t position = path.rfind(separator); // Didn't find if (position == std::string::npos) return path; // Return from after the separator to the end return path.substr(position + 1); } std::string Utils::File::dropBasename(std::string path) { std::string basename = Utils::File::basename(path); if (basename.empty()) return path; size_t position = path.find(basename); if (position == std::string::npos) return ""; // Return from start to before the separator return path.substr(0, position - 1); } std::string Utils::File::extension(std::string path) { size_t position = path.rfind('.'); if ((position == std::string::npos) || // Didn't find (position == 0)) // File name starts with a dot return ""; // Return from after the dot to the end return path.substr(position + 1); } std::string Utils::File::dropExtension(std::string path) { std::string extension = Utils::File::extension(path); if (extension.empty()) return path; size_t position = path.find(extension); if (position == std::string::npos) return ""; // Return from start to (and including) the dot return path.substr(0, position - 1); } // __ _____ ___ _ _ __ // ( (` | | | |_) | | | |\ | / /`_ // _)_) |_| |_| \ |_| |_| \| \_\_/ char Utils::String::back(std::string& str) { // Using the reverse iterator return *(str.rbegin()); } char Utils::String::front(std::string& str) { return *(str.begin()); } void Utils::String::pop_back(std::string* str) { if (str->size() > 0) str->resize(str->size() - 1); } std::string Utils::String::pop_back(std::string& str) { return (str.substr(0, str.size() - 1)); } const char trim_blanks[] = " \t\r\n"; // Characters to be removed std::string Utils::String::ltrim(const std::string& str) { size_t startpos = str.find_first_not_of(trim_blanks); // Found no blanks if (startpos == std::string::npos) return ""; return str.substr(startpos); } std::string Utils::String::rtrim(const std::string& str) { size_t endpos = str.find_last_not_of(trim_blanks); // Found no blanks if (endpos == std::string::npos) return ""; return str.substr(0, endpos + 1); } std::string Utils::String::trim(const std::string& str) { return (Utils::String::ltrim( Utils::String::rtrim( str))); } std::vector Utils::String::split(const std::string& str, char delim) { std::stringstream ss(str); // "buffer" std::string item; // current thing std::vector elems; // all things while (std::getline(ss, item, delim)) elems.push_back(Utils::String::trim(item)); return elems; } bool Utils::String::caseInsensitiveSmallerChar(const char x, const char y) { return (std::tolower(x) < std::tolower(y)); } bool Utils::String::caseInsensitiveSmallerString(const std::string &a, const std::string &b) { return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(), Utils::String::caseInsensitiveSmallerChar); } /** * These Base64 functions are modified versions of * René Nyffenegger's `base64.cpp` and `base64.h`. * * Copyright (C) 2004-2008 René Nyffenegger * * This source code is provided 'as-is', without any express or implied * warranty. In no event will the author be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this source code must not be misrepresented; you must not * claim that you wrote the original source code. If you use this source code * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original source code. * * 3. This notice may not be removed or altered from any source distribution. * * René Nyffenegger rene.nyffenegger@adp-gmbh.ch */ // All allowed characters inside the Base64 domain. static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; // Tells if some character #c belongs to the Base64 charset. static inline bool isBase64(unsigned char c) { return (isalnum(c) || (c == '+') || (c == '/')); } std::string Utils::Base64::encode(std::string str) { // Getting the raw bytes we'll encode // Dark C++ casting magic here. unsigned char const* bytes_to_encode = reinterpret_cast(str.c_str()); unsigned int string_size = str.size(); std::string ret; int i = 0; int j = 0; unsigned char char_array_3[3]; unsigned char char_array_4[4]; while (string_size--) { char_array_3[i++] = *(bytes_to_encode++); if (i == 3) { char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); char_array_4[3] = char_array_3[2] & 0x3f; for(i = 0; (i <4) ; i++) ret += base64_chars[char_array_4[i]]; i = 0; } } if (i) { for(j = i; j < 3; j++) char_array_3[j] = '\0'; char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); char_array_4[3] = char_array_3[2] & 0x3f; for (j = 0; (j < i + 1); j++) ret += base64_chars[char_array_4[j]]; while((i++ < 3)) ret += '='; } return ret; } std::string Utils::Base64::decode(std::string const& encoded_string) { int string_size = encoded_string.size(); int i = 0; int j = 0; int in_ = 0; unsigned char char_array_4[4], char_array_3[3]; std::string ret; while (string_size-- && ( encoded_string[in_] != '=') && isBase64(encoded_string[in_])) { char_array_4[i++] = encoded_string[in_]; in_++; if (i ==4) { for (i = 0; i <4; i++) char_array_4[i] = base64_chars.find(char_array_4[i]); char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; for (i = 0; (i < 3); i++) ret += char_array_3[i]; i = 0; } } if (i) { for (j = i; j <4; j++) char_array_4[j] = 0; for (j = 0; j <4; j++) char_array_4[j] = base64_chars.find(char_array_4[j]); char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; } return ret; } nSnake-3.0.1/src/Misc/Utils.hpp000066400000000000000000000172301236634675000162440ustar00rootroot00000000000000#ifndef UTILS_H_DEFINED #define UTILS_H_DEFINED #include #include #include // mkdir() and off_t #include #include /// Random useful things accumulated over the years. /// namespace Utils { /// Better random number generator. namespace Random { /// Must be called before any of those. void seed(); /// Random number between `min` and `max`. int between(int min, int max); /// Random boolean. bool boolean(); /// Random boolean with chance of #percent. /// @note Precision up to 2 decimal digits. bool booleanWithChance(float percent); }; namespace Time { /// Stops execution for #delay microseconds. void delay_ms(int delay); }; /// File I/O and Operational System's utilities. /// /// Note, I'm using several POSIX functions. /// So the following functions surely aren't /// portable to Windows. /// Other systems are kinda unpredictable. /// namespace File { /// Tells if #path exists. /// /// @note It could be a file, directory or whatever. bool exists(std::string path); /// Returns the file size of #path in bytes. /// /// @return It's size or -1 if it doesn't exist /// (or something strange happened). off_t size(std::string path); /// Creates #path directory hierarchy recursively, /// just like UNIX command `mkdir -p`. void mkdir_p(std::string path); /// Removes recursively all files within directory /// at #path, just like UNIX command `rm -rf`. void rm_rf(std::string path); /// Forcibly removes file within #path. /// @note It doesn't work with directories. void rm_f(std::string path); /// Creates empty file #path. /// /// @note If file already exists, will /// erase everything inside! /// @return If we could create the file at all. bool create(std::string path); /// Writes #contents to #path. /// /// @note If #path doesn't exist, creates it. /// @note If #path exist, overwrites everything on it. void write(std::string path, std::string contents); /// Tells if #path is a directory. /// /// @note Returns false also if something wrong happened. bool isDirectory(std::string path); /// Tells if #path is a regular file /// (not a directory, socket, FIFO device or whatever). /// /// @note Returns false also if something wrong happened. bool isFile(std::string path); /// Lists all files withing #path. /// /// @note The returned vecor is not ordered and /// all file names contain the full #path /// before them. std::vector ls(std::string path); /// Gets the full path of the home directory for /// the user running this program. /// /// @return The path or an empty string. /// @note We guarantee that the path has a trailing '/'. std::string getHome(); /// Gets the user name of the person running this program. std::string getUser(); /// Returns the component of a pathname (file name and extension). /// /// - If we have "/path/to/something.txt" it returns "something.txt" /// - If we have "something.txt" it returns "something.txt" /// /// @note It auto-detects the separator for Windows ('\') /// and UNIX-based systems ('/') /// /// Thanks to this huge list of OS-specific defines: /// http://sourceforge.net/p/predef/wiki/OperatingSystems/ std::string basename(std::string path); /// Returns the full pathname up to the last component. /// /// - If we have "/path/to/something.txt" it returns "/path/to" /// - If we have "something.txt" it returns "" /// std::string dropBasename(std::string path); /// Returns the extension of a file. /// /// @note It doesn't return the dot. /// /// - If we have "/path/to/file.txt" it returns "txt" /// - If we have "filename.DLL" it returns "DLL" /// - If we have ".hidden" it returns "" /// - If we have "none" it returns "" /// /// @note Works with full paths or single filenames. std::string extension(std::string path); /// Returns the filename without it's extension. /// /// @note Works with full paths or single filenames. std::string dropExtension(std::string path); }; namespace String { /// Converts from any type that supports the /// << operator to std::string. /// /// @note Due to templates, we must place the definition /// on the header. /// @note Call it like `Utils::String::toString(var)` template inline std::string toString(T const& x) { std::ostringstream o; if (!(o << x)) throw std::runtime_error("Utils::String::toString"); return o.str(); } /// Converts from std::string to any type /// that supports the << operator. /// /// @note Due to templates, we must place the definition /// on the header. /// @note Call it like `Utils::String::convert(string, var)` template inline void convert(std::string const& s, T& x, bool failIfLeftOverChars=true) { std::istringstream i(s); char c; if (!(i >> x) || (failIfLeftOverChars && i.get(c))) throw std::runtime_error("Utils::String::convert"); } /// An easier way to call previous function. /// @note Call it like `Utils::String::to(string)` template inline T to(std::string const& s, bool failIfLeftOverChars=true) { T x; convert(s, x, failIfLeftOverChars); return x; } /// Returns the last character on the string. /// /// @note C++11 compatibility! /// @thanks http://stackoverflow.com/a/4884579 char back(std::string& str); /// Returns the first character on the string. /// /// @note C++11 compatibility! char front(std::string& str); /// Destructively removes the last character of #str. /// @note C++11 compatibility! /// @thanks http://stackoverflow.com/a/2311025 void pop_back(std::string* str); /// Non-destructively removes the last character of #str. /// (returns a new string) std::string pop_back(std::string& str); /// Removes all space on the left of `str`. std::string ltrim(const std::string &str); /// Removes all space on the right of `str`. std::string rtrim(const std::string& str); /// Removes all space on both sides of `str`. std::string trim(const std::string& str); /// Splits `str` according to `delimt`. /// /// @return A vector of strings, without the delimiter. /// std::vector split(const std::string& str, char delim); /// Tells if some character is smaller than other /// independently of it's case (upcase or smallcase). /// bool caseInsensitiveSmallerChar(const char x, const char y); /// Tells if a whole string is smaller than other /// independently of it's case (upcase or smallcase). /// /// This allows sorting a vector of strings case insensitively. /// in a very easy way. Look at it: /// /// std::sort(vector.begin(), /// vector.end(), /// Utils::String::caseInsensitiveSmallerString); /// /// Thanks to: Philip Nicoletti /// bool caseInsensitiveSmallerString(const std::string &a, const std::string &b); }; /// Utilities to encode and decode from the Base64 format. namespace Base64 { /// Transforms #str into a Base64 equivalent. std::string encode(std::string str); /// Transforms a Base64-encoded #str into it's regular string equivalent. std::string decode(std::string const& s); }; }; // Useful #defines collected over the years. /// Deletes #pointer if valid, invalidating it after. #define SAFE_DELETE(pointer) \ { \ if (pointer) \ { \ delete(pointer); \ pointer = NULL; \ } \ } /// Tells the compiler we're not using #thing as an argument. /// Don't forget ';' after it! #define UNUSED(thing) \ { \ (void)(thing); \ \ } #endif //UTILS_H_DEFINED nSnake-3.0.1/src/main.cpp000066400000000000000000000013551236634675000151710ustar00rootroot00000000000000#include #include #include #include #include #include int main(int argc, char *argv[]) { try { // Settings Globals::init(); Globals::loadFile(); Arguments::parse(argc, argv); // Misc Initializations Utils::Random::seed(); Ncurses::init(); Colors::init(); // Actually running the game StateManager states; states.run(); // Finishing things Globals::saveFile(); Ncurses::exit(); Globals::exit(); } catch (...) { // I dont really have a nice exception-handling scheme right // now. I must learn how to properly deal with them. Ncurses::exit(); return 666; } return 0; }