commit 3343d8711bfc6ecd2ffcccedc721d0ff5cef6d63
Author: Squishy (C6H12O6+NaCl+H2O) <106439598+SweetSea-ButImNotSweet@users.noreply.github.com>
Date: Thu Apr 11 08:33:58 2024 +0700
V0 version
Add ``.gitignore``
Update ``.vscode\settings.json``
Main file changed a bit
Replace every single ``io.open`` into ``fs.read()``
Add ``input.waiting2trigger`` as buffer for too quick inputs
Replace ``binser`` with ``bitser``
Add the missing buffer logical code in training mode
Add a debug connector
Not a big update
Update VirtualControl.lua
Update in vctrl system
Trimming some unnecessary empty lines in classic library
Update virtual control stuff
Replace ``table.getn`` with ``#`` and ``scene`` with ``SCENE``
Renaming and moving some modules
Removing unnecessary ``local mino = {...}``
Add loading screen
Update loading screen
Apply replay patch
Not showing virtual control on computer
Adding touch screen configuration scene (placeholder)
Fix loading screen
update virtual control texture
Do some preparation for touch config screen
Quick patch
Compress background
Not important uodates
Small changes on how virtual key call action
Add ``SCENE:onInputMove``
Apply V2.2 patch
Clean up unnecessary imports
Test
.
Remove a redudant global variable
Small change
Split up alpha number
Sorting code
Update storeInput function
Optimize replay storing, saving and reading
Add VCTRL.export (for saving feature)
Remove unnecessary imports
Redesign loading screen
Replace loading screen
Make a simple BUTTON module
Update BUTTON module
Update button module
Add new callback
Add new callback
TEST
Update simple-button module
Update simple button module
Set default draw function for button
Add scene type notation
TEST
Not important updates
Design a error screen
Small update
Remove error key
Update
TEST
TEST
Test
TEST
TEST
Update button module
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
test
TEST
TEST
TEST
test
TEST
test
Fix a bug in VCTRL module that affect to SCENE:onInputRelease
Moving VCTRL related calls and adding buttons for name entry screen
Add type notation
Update modules
Final update for touch configuration scene
Fix 2 buttons can be highlighted at the same time in simple-button module
Narrow the safe border
Remove id = b (it was there for test)
Update of touch configuration scene
Add touch gesture for replay and input configuration scene
Add buttons for Replay, add MENU to go out after finishing game or in 20G Training mode
TEST
Fix some bugs (TEST)
Fix lỗi giữa đêm
Fix bug again
It should work imo
TEST
Fix SCENE:onInputMove{type="touch"} is not working
Fix bug once again (DONE!)
Temproraily allowing save
Fix settings module
Fix VCTRL.exportAll()
Fix VCTRL.exportAll returns userdata
Reverse a change
Fix forgetting to import virtual control settings
Fix grid drawing
Fix bugs related to the first time launching game
Add README file
Add README file
Update README and add LICENSE files
Update README
Add TV remote code
Disable debug code
Fix Android code
Small fix
Rename LICENSE to COPYING
Moving scene.lua to modules folder
Add new libraries
Make a new FILE API and add a simple error screen in case most thing went down
Change special code, add a way to skip keys
Update icon + README file
Rename screenshot file
Update README
Updating README file
Replace loading screen
Update README
Update virtual control texture
Fix virtual button method
Update README
Add icon font
Add importing and exporting replays
Update touch control
Update conf.lua
Replacing font, to avoid license issue
convert indents to spaces
Update font related stuff
Replace font
Updating README file
Update virtual control texture
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..be29303
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+.vscode
+libAndroid
+*.ini
+.DS_Store
+Thumbs.db
+Icon?
+.Trash
+.file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..1deeeb5
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,15 @@
+{
+ "Lua.runtime.version": "LuaJIT",
+ "Lua.runtime.special": {
+ "love.filesystem.load": "loadfile"
+ },
+ "Lua.workspace.library": [
+ "${3rd}/love2d/library",
+ "C:/Users/Harry/AppData/Roaming/Code/User/globalStorage/sumneko.lua/addonManager/addons/love2d/module/library",
+ "C:/Users/Harry/AppData/Roaming/Code/User/globalStorage/sumneko.lua/addonManager/addons/lldebugger/module/library"
+ ],
+ "Lua.workspace.checkThirdParty": false,
+ "githubPullRequests.ignoredPullRequestBranches": [
+ "main"
+ ]
+}
\ No newline at end of file
diff --git a/COPYING.txt b/COPYING.txt
new file mode 100644
index 0000000..c031602
--- /dev/null
+++ b/COPYING.txt
@@ -0,0 +1,29 @@
+Tromi Copyright (c) 2024 mycophobia (admin@mycophobia.org)
+What consitutes the portion of Tromi that is not images, videos, sounds, music, or any code that is not already substantially part of Cambridge, is licensed under the GNU GPL version 3 or later. See file "LICENSE/GNU_GPLv3.txt" for full text.
+
+Background images and videos for Tromi Copyright (c) Pixabay (pixabay.com)
+All images and videos used as backgrounds are used under the Pixabay terms and conditions as of January 9, 2024. See file "LICENSE/Pixabay_TOS.txt" for full text.
+
+Music for Tromi Copyright (c) 2024 Jerry Martin (info@jerrymartinmusic.com), used with permission.
+
+Cambridge Copyright (c) 2018-2019 Joe Zeng
+
+License text for portions of Tromi that are also substantially part of Cambridge:
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/LICENSE/GNU_GPLv3.txt b/LICENSE/GNU_GPLv3.txt
new file mode 100644
index 0000000..3c4e824
--- /dev/null
+++ b/LICENSE/GNU_GPLv3.txt
@@ -0,0 +1,189 @@
+GNU GENERAL PUBLIC LICENSE
+
+Version 3, 29 June 2007
+
+Copyright © 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
diff --git a/LICENSE/Pixabay_TOS.txt b/LICENSE/Pixabay_TOS.txt
new file mode 100644
index 0000000..23cea99
--- /dev/null
+++ b/LICENSE/Pixabay_TOS.txt
@@ -0,0 +1,145 @@
+
+Pixabay Terms of Service
+
+Last updated: January 9, 2024
+
+ 1.The basics.
+
+ Welcome to Pixabay! Pixabay is a vibrant community of creatives sharing royalty-free content. These Terms of Service ("Terms") apply to the pixabay.com website, related websites, software, mobile apps, plug-ins and other Pixabay-operated services (collectively, the "Service(s)").
+
+ These Terms govern the relationship between Pixabay, a Canva Germany GmbH brand ("we", "our" or "Pixabay") and any user of the Services ("you", "your" or "User"), including in certain circumstances, the relationship between Users. By using the Services (including by downloading and using Content from the Services, or contributing Content to the Services):
+ you agree that you are entering into these Terms with us which will become a legally binding agreement between you and us; and
+ you represent and warrant that you have the full right, power and authority to agree to and be bound by these Terms and to fully perform all of your obligations listed in these Terms.
+
+ If you don’t agree to these Terms, please don’t use the Services.
+
+ Children may not access or use the Services unless their use is directly authorized by their parent, guardian or another authorized adult who agrees to be bound by these Terms. For the purposes of these Terms, a child is a person under the age of 13 (or the minimum legal age required to provide consent for processing of personal data in the country where the child is located, noting 16 is the minimum legal age in Germany).
+
+ We reserve the right, at our sole discretion, to change or modify these Terms at any time, including the right to cancel or change the licenses granted by these Terms. If we do this, we will post the most current version of these Terms on this page. We will seek to provide you with reasonable advance notice of any change to the Terms that, in our sole determination, materially adversely affect your rights or your use of the Services. We may provide you with this notice via the Services and/or by direct message on the Services and/or by posting on Pixabay’s public forum. By continuing to use the Services after any revised Terms become effective, you agree to be bound by the new Terms.
+ 2.Content.
+
+ In these Terms when we refer to "Content" we are referring to the content which is available on the Services, but excluding any third party paid advertising or sponsored content (over which Pixabay has no control and we do not grant any license). Content includes, but isn’t limited to the following items:
+ "Images", which means photographs, vector graphics, drawings and illustrations.
+ "Videos", which means moving images, animations, film footage and other audio-visual representations and content.
+ "Audio", which means music, sounds, sound effects and other audio representations and content.
+ "Other Media", which means any other media or content which is visual or audio in nature, or a combination of these.
+ 3.CC0 License
+
+ Some of the Content made available for download on the Service is subject to and licensed under the Creative Commons Zero (CC0) license ("CC0 Content"). CC0 Content on the Service is any content which lists a "Published date" prior to January 9, 2019. This means that to the greatest extent permitted by applicable law, the authors of that work have dedicated the work to the public domain by waiving all of his or her rights to the CC0 Content worldwide under copyright law, including all related and neighboring rights. Subject to the CC0 License Terms, the CC0 Content can be used for all personal and commercial purposes without attributing the author/ content owner of the CC0 Content or Pixabay.
+ 4.Content License granted to you for Content (other than CC0 Content).
+
+ Subject to the Prohibited Uses described below (which you agree and acknowledge you are not entitled to engage in), when you download any Content that is not CC0 Content from the Services, we grant you an irrevocable, worldwide, perpetual (or as long as allowed by law), non-exclusive and royalty-free right to download, use, copy, modify or adapt the Content for commercial or non-commercial purposes ('Content License'). The rights granted are non-exclusive, meaning that we may also grant other users the same rights in the same Content.
+
+ You agree and acknowledge that the following items are "Prohibited Uses" and the Content License which we grant to you is conditional on you not engaging in any of the Prohibited Uses.
+
+ You cannot sell or distribute the Content (either in digital or physical form) on a Standalone basis. This includes selling or distributing Content on a Standalone basis as an image, audio, video, NFT or other digital file (including through a stock media platform), as well as a print, wallpaper, poster or on merchandise or on other physical products.
+
+ When we refer to "Standalone" we mean where no creative effort has been applied to the Content and it remains in substantially the same form as it exists on the Services. To help illustrate this, here are some examples:
+ using the Content in its original form or solely using a filter, changing colors, resizing or cropping the Content remains Standalone use.
+ using the Content with a combination of images, videos, audio files, other media, text, illustrations, background features and editing techniques is not Standalone use, so long as the combined effect is to make a "new" creative work.
+ If Content depicts any trademarks, logos or brands (whether two- or three-dimensional), you cannot use that Content for commercial purposes in relation to goods and services, in particular not print that Content on merchandise or other physical products for sale.
+ You cannot use Content in any immoral or illegal way, especially Content which features a recognisable person. You cannot use any Content on or in conjunction with anything pornographic, obscene, offensive (including but not limited to in relation to adult entertainment venues, escort services, drug use, dating services, in a way which portrays someone as suffering from, or medicating for, a physical or mental ailment), illegal, immoral, infringing, defamatory, hateful, threatening or libellous in nature, in a political context (such as the promotion, advertisement or endorsement of any party, candidate, or elected official or in connection with any political party or viewpoint) or in breach of any law, regulation or industry code, or in any way which infringes the rights of any person or entity.
+ You cannot use Content in a misleading or deceptive way. You cannot use Content in a way that is misleading or deceptive, including without limit:
+ by suggesting that any depicted person, brand, organization or other third party endorses or is affiliated with you or your goods or services, unless permission has been granted; or
+ by giving the impression that Content was created by you, or a person other than the intellectual property rights holder of the Content (including without limitation, by claiming or giving the impression that you hold ownership of, or exclusive rights to, the Content).
+ You cannot use any of the Content as part of a trade-mark, design-mark, trade-name, business name or service mark.
+
+ In addition to the Prohibited Uses, please be aware that certain Content (including CC0 Content) may be subject to additional intellectual property rights (such as copyrights, trademarks, design rights), moral rights, proprietary rights, property rights, privacy rights or similar. This includes but is not limited to identifiable people, logos, brands of all kinds (whether two- or three- dimensional), goods or services, buildings and architecture, audio or video samples and organisations.
+
+ Before using any Content (including CC0 Content), you must consider whether you require the consent of a third party or a license to use the Content. If your use of the Content is for commercial purposes (e.g. in conjunction with the sale or promotion of a product or service) then it is likely that you will need consent or a license. Responsibility for determining whether permissions are needed always rests solely and exclusively with you. We do not warrant that any consents or licenses have been obtained in relation to any Content, and we expressly disclaim any and all responsibility and liability in relation to such matters.
+ 5.Attribution.
+
+ You do not need to credit Pixabay or the contributor of Content when you use it, but it is certainly appreciated when you do so - especially when the use is of an editorial nature, where photo credits are more customary. You can provide credit in the following format: "by [Contributor] via Pixabay".
+ 6A.Uploading Content.
+
+ We are always grateful when you choose to contribute Content to our Services. For us to be able to make the Content available for other users, it is important that we receive specific rights and promises from you as a contributor. You are responsible for the Content you upload to the Services and you do so subject to the following terms and conditions:
+ When you upload any Content to the Services you grant Pixabay an irrevocable, perpetual (or as long as allowed by law), worldwide, non-exclusive, royalty-free license (with the right to sub-license) to use, store, market, reproduce, display, exhibit, broadcast, publicly perform, enhance, alter, adapt, modify or download the Content (in whole or in part) for any purpose whether now known or created in the future, including both commercial and non-commercial purposes. This includes the right to redistribute the Content under the Content License, or any other license or terms offered by Pixabay now or in the future, including via an API.
+ You expressly represent and warrant that:
+ you and you alone own all rights, title and interest in and to the Content you upload to the Services; and
+ the Content does not, and use of the Content in accordance with these Terms will not, infringe the intellectual property rights, moral rights, proprietary rights, property rights, privacy rights or similar of any person or entity; and
+ you have obtained (and shall retain a copy of) any and all releases, permissions or licenses necessary to enable the use of the Content in accordance with these Terms. This includes non-exclusive, perpetual (or as long as allowed by law), irrevocable, worldwide, and royalty-free model and/or property releases, and/or any other permissions necessary concerning the use of the Content for any purpose, without any conditions, unless such conditions are required by law; and
+ you agree to execute such further documents as may be reasonably required to give effect to Pixabay’s rights
+ You acknowledge and agree that your Content will be made available to the public for personal and commercial use of third parties subject to these Terms, without providing you attribution or compensation. You acknowledge and agree that Pixabay, its affiliates, and any third party partners specifically authorised by Pixabay may use your Content and your general usage and activity data from Pixabay (which includes things like your analytic events and search queries) to develop, improve and provide their services, including through the use of machine learning technology. If you don’t want your Content to be used for machine learning, you can manage how we use your Content by opting out of future AI training by updating your preferences in your account settings at any time.
+ You agree not to upload Content that is:
+ sexually explicit or pornographic;
+ offensive, defamatory or discriminatory, or violates any law, statute or regulation;
+ promotes or creates a risk of physical or mental harm.
+
+ We reserve the right to remove any Content from the Service at any time and for any reason, at our discretion. This applies in particular to defective files, inferior quality content, content which is in violation of these Terms and for any reason relating to potential legal issues.
+ 6B.Uploading Content created with generative AI technology.
+
+ You may upload Content you have created with generative AI technology (i.e. machine learning tools that generate content from text prompts or other inputs) to the Services. If you do, the following specific conditions apply, in addition to section 6.
+ You are responsible for ensuring that the terms of the generative AI technology you have used permit you to grant the license under section 6(a) and give the warranties outlined in section 6(b).
+ You must clearly label any Content which is AI-generated by selecting the "AI-generated" checkbox on the upload page.
+ You must not tag any Content which is AI-generated with the names of any real people, artists, trademarks or brands.
+ You must not upload any Content which is AI-generated that mimics the style of another artist or attempts to reproduce copyright or trademark protected material, or the image of a real person.
+ 7.Use of the Service.
+
+ You may be required to register with us to access and use certain features of the Service. If you choose to register for the Services, you agree to provide and maintain true, accurate, and current information as prompted by the Services’ registration form. Registration data and other information you provide through the Services is subject to our Privacy Policy. You are responsible for any activity conducted on your account. You may not allow any other party to access or use the Services with your unique username, password, or other security code. Your username must not infringe any third party rights or be offensive, vulgar or obscene.
+
+ In using the Services, you acknowledge and agree that:
+ Data mining, extraction, scraping and the use of programs or robots for automatic data collection and/or extraction of digital data on the Services and/or the content available therein is strictly prohibited for all unauthorised purposes, including without limitation for machine learning purposes.
+ The technology and software underlying or distributed in connection with the Service is owned by us and our licensors, affiliates and partners. We do not grant any rights in such software to you. You must not copy, modify, create a derivative work from, reverse engineer, reverse assemble or otherwise attempt to discover any source code, sell, assign, sublicense, or otherwise transfer any right in such technology or software, unless we grant our express permission in advance.
+ Bulk, large-scale or systematic copying of Content is strictly prohibited unless explicit permission has been granted by us.
+ You will not yourself or through any third party:
+ copy, replicate, decompile, reverse-engineer, attempt to derive the source code of, modify, or create derivative works of the Service, or any part thereof;
+ use or compile any Content to replicate a similar or competing service to the Services, or copy the look or feel or the Services;
+ use the Services to transmit unsolicited messages or engage in spamming;
+ use the Services to store or transmit any virus or malicious code;
+ impersonate any other person or entity on the Services, or impersonate any non-existent relationship with such persons;
+ use or exploit any metatags or other hidden texts containing our name or any of our trademarks without our express written consent;
+ collect, store or use any personal information, including member names or profiles, about other users;
+ access the Services for purposes of performance benchmarking or for building or marketing a competitive product; or
+ bypass any measures we may use to prevent or restrict access to the Services, including without limitation features that prevent or restrict use or copying of any content or enforce limitations on use of the Services or the Content.
+ The compilation of the Content is copyrighted as a collective work and/or compilation in accordance with copyright laws, international conventions and other intellectual property laws.
+ 8.Term and Termination.
+
+ These Terms shall take effect the first time you access the Services and shall continue in full force and effect until your account is deleted or terminated. If Pixabay, in its reasonable discretion, determines that you, your Content or your use of the Services violate these Terms, Pixabay may take one of the following actions: delete the prohibited Content; (ii) suspend your access to the Services; (iii) terminate and delete your account; (iv) permanently ban you from using the Services; and/or (v) disclose the prohibited Content or activity to appropriate government authorities. Upon termination, your right to use the Services will immediately cease. The Services may not be accessed by any Users previously removed from the Services by Pixabay.
+ 9.Reporting and takedown policies.
+
+ Pixabay respects the rights of creatives. If you believe that your intellectual property rights have been infringed by Content accessible through the Services, please email us at info@pixabay.com and include the following information:
+ Identification of the intellectual property right you believe has been infringed.
+ Identification of the Content that you believe is infringing your rights, including a URL link to where that Content appears on the Services.
+ Your contact information, such as your email address.
+ A statement that you have a good faith belief that use of the Content in the manner complained of is not authorized by the intellectual property rights owner, its agent, or the law.
+ A declaration that the above information is accurate and that you are (or are authorized to act on behalf of) the intellectual property rights owner.
+
+ If you believe that any Content made available on or via the Services contains a violation of the law, please report this to info@pixabay.com.
+
+ Nothing in this section constitutes legal advice. We suggest you consult an attorney regarding your rights and obligations under applicable laws.
+ 10.Infringement claims.
+
+ If you find out – from Pixabay or somewhere else – that there is a claim relating to any Content and Pixabay might be liable, you have to stop using the Content immediately, at your own expense. This applies whether the claim is threatened, potential or actual. You also have to immediately delete or remove the Content from your premises, computer systems and storage (electronic or physical); ensure that your clients, printers or ISPs do likewise; and let Pixabay know as soon as possible by emailing info@pixabay.com.
+ 11.Indemnification.
+
+ You agree to indemnify, release and hold harmless Pixabay and its affiliates from and against any and all loss, expenses, damages, and costs, including without limitation reasonable attorneys fees, resulting from:
+ your violation of the Terms (whether directly or indirectly); and
+ claims brought by any third parties arising out of your use of the Services.
+
+ If a claim is brought by any third party, you must immediately provide us with all information that is necessary for an examination of the claim and defence. Any further claims for damages by us against you remain unaffected.
+ 12.Dispute resolution and applicable law. https://ec.europa.eu/consumers/odr/.
+
+ Pixabay is neither obliged nor willing to participate in an arbitration procedure within the meaning of the German Consumer Dispute Resolution Act (VSBG). Pixabay will endeavour to resolve any disagreements amicably. Our email address is info@pixabay.com.
+
+ The law of the Federal Republic of Germany applies to the contractual relationship between the users and Pixabay. As far as permissible, Berlin is agreed as the place of jurisdiction.
+ 13.Warranty and liability.
+
+ THE SERVICES AND CONTENT ARE PROVIDED "AS IS". WE OFFER NO WARRANTY, EXPLICIT OR IMPLIED, REGARDING ANY CONTENT (INCLUDING ANY THIRD PARTY OR SPONSORED CONTENT OR ANY LINKS), THE SERVICES, THE ACCURACY OF ANY INFORMATION, OR ANY RIGHTS OR LICENSES UNDER THIS AGREEMENT INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+
+ PIXABAY DOES NOT REPRESENT OR WARRANT THAT THE SERVICES OR ANY CONTENT WILL MEET YOUR REQUIREMENTS OR THAT THEIR USE WILL BE UNINTERRUPTED OR ERROR FREE.
+
+ ALTHOUGH PIXABAY DOES ITS BEST TO ENSURE THAT UPLOADED CONTENT IS NOT MISUSED OR USED CONTRARY TO THE TERMS, PIXABAY CANNOT BE HELD RESPONSIBLE FOR THE ACTS OR OMISSIONS OF ITS USERS, INCLUDING ANY MISUSE OR ABUSE OF ANY CONTENT YOU UPLOAD.
+
+ PIXABAY SHALL NOT BE LIABLE TO YOU OR TO ANY OTHER PERSON OR ENTITY FOR ANY GENERAL, PUNITIVE, SPECIAL, INDIRECT, CONSEQUENTIAL OR INCIDENTAL DAMAGES, OR LOST PROFITS OR ANY OTHER DAMAGES, COSTS OR LOSSES ARISING OUT OF YOUR USE OF THE SERVICES OR ANY CONTENT.
+ 14.Versions and Severability.
+
+ These Terms supersede any prior representations, agreements, or understandings between you and Pixabay, whether written or oral, with respect to the Services including previous versions of the Terms. Any non-English translations of these Terms are provided for convenience only. The English version of these Terms will control.
+
+ If one or more of the provisions in this agreement is found to be invalid, illegal or unenforceable in any respect, the validity, legality and enforceability of the remaining provisions should not be affected. Such provisions should be revised only to the extent necessary to make them enforceable.
+ 15.Prize Draws and Promotions.
+
+ From time to time, Pixabay may conduct prize draws, contests or promotions (“Promotion”), which will be governed by separate additional terms and conditions, which will be published on the Services or otherwise notified to you by Pixabay. By participating in any Promotion, you agree to those additional terms and acknowledge our Privacy Policy.
+ 16.EU Monthly Active Users.
+
+ In accordance with requirements under the EU Digital Services Act, the average number of EU monthly active users of the Service between January 2023 - June 2023 was 3,110,190.
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1384088
--- /dev/null
+++ b/README.md
@@ -0,0 +1,81 @@
+# Tromi (ANDROID port)
+A old-fashioned but modern block-stacking game. Made with :heart: by [mycophobia](https://mycophobia.org/tromi).
+Ported to Android (mobile and TV) by SweetSea with on-screen control (with some necessary changes)
+
+# How to play
+> :warning: There is no Android build (aka APK file) right now. If you see any pre-built APK files on the internet. Please be aware
+## For mobile
+### Install
+1. In the [Releases tab](https://gitea.com/SweetSea-ButImNotSweet/tromi_mobile/releases) and click on ``Source code (zip)``
+2. Extract the folder inside to somewhere you can easy to navigate
+3. Open the folder ``tromi_mobile``, select all files, then add to a ``zip`` archive and pick a name that easy to remember, example: ``tromi_moblie.zip``
+4. Rename the file extension, from ``zip`` to ``love`` (``tromi_mobile.zip`` to ``tromi_mobile.love``)
+5. Download [``LÖVE for Android``](https://love2d.org) (You should get version 11.5)
+6. Install love2d engine (extra steps may require, you can read [this article from WikiHow](https://www.wikihow.com/Install-APK-Files-on-Android) if this is the first time for you to install app from APK file)
+7. Now, press Home (or back to app launcher), you can see two icons: ``LÖVE for Android`` and ``LÖVE Launcher``. You should use the ``LÖVE Launcher`` to launch Tromi
+### Launch
+1. Launch ``LÖVE Launcher``, an file picker will appear
+2. Navigate to where you put the game file, example ``tromi_mobile.love``
+3. Pick the file and enjoy
+
+## For TV
+> :warning: Before I tell you how, I need to tell you something
+> I don't really recommended playing Tromi on TV if you ***don't want*** your electricity bill to be a slap in the face.
+> If you can, **plug in a keyboard or gamepad** and play the original Tromi instead of this one.
+> You can still use the remote, but remember that it ***is not designed for gaming***. And if you lose before the speed hits 20G, I implore you ***not to throw the remote at your TV and then curse me because your performance is worse than when you play on the keyboard***.
+### Installion
+1. On other device, do the step 1 - 5 in ``Install for mobile`` part
+2. Install this file manager on your TV: https://play.google.com/store/apps/details?id=com.alphainventor.filemanager. Once you installed, you should see [this icon](https://play-lh.googleusercontent.com/9PzlG2XGr5sQDf5925tlZhqluOreI2cwzp-FOZiuj545Kt1Gk5EE9J4IdGsD7e5xWw=w240-h480-rw)
+3. Opening the application, click on ``Access from...`` (network/other devices/etc.)
+4. Transfer 2 downloaded files to TV
+5. Install LÖVE for Android application (see step 6 in ``Install for TV`` section)
+6. *(First time only)* Open the ``tromi_mobile.love``. When the file manager asks you to open the file as what. Select ``Other``, then select ``LÖVE for Android`` and select ``Always``
+7. Once you are in the key configuration scene, type ``88663366``. An predefined keybind will be set automatically, but you can now make a different one if you'd like to
+
+### Launch
+Navigate to where you put ``tromi_mobile.love`` in the File manager (the one you downloaded from the link in Install for TV section), just opening and Tromi should be launched.
+
+# Differences from original Tromi
+> I must make this list to follow the used license (GNU GPL v3)
+> :no_entry: There are ***very much*** breaking changes right now, and I can't always finish this list. I may try hard to do it.
+* No differences in gameplay
+* Files will be saved into ``Android/data/org.love2d.android/tromi_mobile`` instead the location where the game files in
+* Add ``simple-button`` module, made by me (SweetSea)
+* All UIs are touch-able
+* Add on-screen buttons
+* Replaced icons for 3 direction buttons (Left, Down, Right), using from Techmino's font (outdated image)
+
+* Add a special pre-made keybind for Android TV (only supports TV models have their remote has numerical area (0-9))
+* Changes the way to input secret code to activate Pentominoes mode
To insert the left arrow, tap on the left, so does to right arrow.
+* Add a loading screen ~~(this need to be updated later)~~
+* Update ``binser`` library, this fixes some weird bugs related to saving
+* Replaced old Cambridge's ``config`` module with the new one inspired by "the sequel of Techmino"s ``SETTINGS`` module
+
+# TODO
+- [ ] Add a way to export ~~replay~~ data for Android > 11
+- [x] Revert ``bitser`` with ``binser`` (if and only if I can make it works)
+- [x] Design a new on-screen buttons skin (the current one is come from [C₂₉H₂₅N₃O₅](https://github.com/C29H25N3O5), I am aware that it's not fit to Tromi's design language)
+- [ ] Updating on-screen control configuration screen
+- [ ] (Low priority) Design a new menu screen
+
+# License (GNU GPLv3)
+Please read ``COPYING.txt`` for more information.
+
+A small note about the music:
+> Only mycophobia right now having the permission to music from Jerry Martin, I haven't got.
+>
+> I sent an email about this issue (not mentioning about this game), but I haven't got any replies from them (I checked both my main inbox and spam too, nothing)
+>
+> Both me and mycophobia can't give you a sub-license if you ask. What can I do is suggesting you to send an email about this (there is a chance that you may not receive any replies, but it's better than do nothing)
+
+# Special thanks
+* mycophobia for writing the original Tromi
+* MrZ_26 for the base of ``VCTRL`` module (yea I stole from him his code ;-; )
+* C₂₉H₂₅N₃O₅ for his virtual key design (used while during inital development), and icon font (from Techmino).
+
+# Don't forget to check
+* [Original Tromi](https://mycophobia.org/tromi)
+* [Leaderboard](https://mycophobia.org/)
+
+* [Cambridge](https://github.com/Cambridge-stacker/Cambridge)
+* [Techmino](https://github.com/26F-Studio/Techmino)
\ No newline at end of file
diff --git a/char.lua b/char.lua
new file mode 100644
index 0000000..5c25e4d
--- /dev/null
+++ b/char.lua
@@ -0,0 +1,415 @@
+local function utf8_convert(n)
+ local floorint = math.floor
+ local char = string.char
+
+ assert(type(n)=='number',"Wrong type ("..type(n)..")")
+ assert(n>=0 and n<2^31,"Out of range ("..n..")")
+ if n<2^7 then return char(n)
+ elseif n<2^11 then return char(192+floorint(n/2^06),128+n%2^6)
+ elseif n<2^16 then return char(224+floorint(n/2^12),128+floorint(n/2^06)%2^6,128+n%2^6)
+ elseif n<2^21 then return char(240+floorint(n/2^18),128+floorint(n/2^12)%2^6,128+floorint(n/2^06)%2^6,128+n%2^6)
+ elseif n<2^26 then return char(248+floorint(n/2^24),128+floorint(n/2^18)%2^6,128+floorint(n/2^12)%2^6,128+floorint(n/2^06)%2^6,128+n%2^6)
+ elseif n<2^31 then return char(252+floorint(n/2^30),128+floorint(n/2^24)%2^6,128+floorint(n/2^18)%2^6,128+floorint(n/2^12)%2^6,128+floorint(n/2^06)%2^6,128+n%2^6)
+ end
+end
+
+local L={
+ zChan={-- F0000 - F003F
+ none= 0xF0000,
+ normal= 0xF0001,
+ full= 0xF0002,
+ happy= 0xF0003,
+ confused= 0xF0004,
+ grinning= 0xF0005,
+ frowning= 0xF0006,
+ tears= 0xF0007,
+ anxious= 0xF0008,
+ rage= 0xF0009,
+ fear= 0xF000A,
+ question= 0xF000B,
+ angry= 0xF000C,
+ shocked= 0xF000D,
+ ellipses= 0xF000E,
+ sweatDrop= 0xF000F,
+ cry= 0xF0010,
+ cracked= 0xF0011,
+ qualified= 0xF0012,
+ unqualified= 0xF0013,
+ understand= 0xF0014,
+ thinking= 0xF0015,
+ spark= 0xF0016,
+ },
+ mino={-- F0040 - F007F
+ Z=0xF0040,
+ S=0xF0041,
+ J=0xF0042,
+ L=0xF0043,
+ T=0xF0044,
+ O=0xF0045,
+ I=0xF0046,
+
+ Z5=0xF0047,
+ S5=0xF0048,
+ P= 0xF0049,
+ Q= 0xF004A,
+ F= 0xF004B,
+ E= 0xF004C,
+ T5=0xF004D,
+ U= 0xF004E,
+ V= 0xF004F,
+ W= 0xF0050,
+ X= 0xF0051,
+ J5=0xF0052,
+ L5=0xF0053,
+ R= 0xF0054,
+ Y= 0xF0055,
+ N= 0xF0056,
+ H= 0xF0057,
+ I5=0xF0058,
+
+ I3=0xF0059,
+ C= 0xF005A,
+ I2=0xF005B,
+ O1=0xF005C,
+ },
+ icon={-- F0080 - F00FF
+ menu= 0xF0080,
+ music= 0xF0081,
+ language= 0xF0082,
+ back= 0xF0083,
+ play_pause= 0xF0084,
+ info= 0xF0085,
+ help= 0xF0086,
+ mute= 0xF0087,
+ volume_up= 0xF0088,
+ volume_down= 0xF0089,
+ retry_spin= 0xF008A,
+ filledLogo= 0xF008B,
+ hollowLogo= 0xF008C,
+ toUp= 0xF008D,
+ toDown= 0xF008E,
+ toLeft= 0xF008F,
+ toRight= 0xF0090,
+ checkMark= 0xF0091,
+ crossMark= 0xF0092,
+ musicMark= 0xF0093,
+ infoMark= 0xF0094,
+ warnMark= 0xF0095,
+ console= 0xF0096,
+ globe= 0xF0097,
+ video_camera= 0xF0098,
+ settings= 0xF0099,
+ mrz= 0xF009A,
+ apple= 0xF009B,
+ home= 0xF009C,
+ cross_thick= 0xF009D,
+ num0InSpin= 0xF009E,
+ num1InSpin= 0xF009F,
+ num2InSpin= 0xF00A0,
+ num3InSpin= 0xF00A1,
+ num4InSpin= 0xF00A2,
+ play= 0xF00A3,
+ pause= 0xF00A4,
+ nextFrame= 0xF00A5,
+ yen= 0xF00A6,
+ dollar= 0xF00A7,
+ euro= 0xF00A8,
+ pound= 0xF00A9,
+ bitcoin= 0xF00AA,
+ onebag= 0xF00AB,
+ export= 0xF00AC,
+ import= 0xF00AD,
+ trash= 0xF00AE,
+ loadOne= 0xF00AF,
+ saveOne= 0xF00B0,
+ loadTwo= 0xF00B1,
+ saveTwo= 0xF00B2,
+ zBook= 0xF00B3,
+ rankX= 0xF00B4,
+ rankU= 0xF00B5,
+ rankA= 0xF00B6,
+ rankB= 0xF00B7,
+ rankC= 0xF00B8,
+ rankD= 0xF00B9,
+ rankE= 0xF00BA,
+ rankF= 0xF00BB,
+ rankZ= 0xF00BC,
+ rankS= 0xF00C2,
+ speedOneEights= 0xF00BD,
+ speedOneHalf= 0xF00BE,
+ speedOne= 0xF00BF,
+ speedTwo= 0xF00C0,
+ speedFive= 0xF00C1,
+ bone= 0xF00C3,
+ invis= 0xF00C4,
+ bomb= 0xF00C5,
+ garbage= 0xF00C6,
+ copy= 0xF00C7,
+ tas= 0xF00C8,
+ pencil= 0xF00C9,
+ magGlass= 0xF00CA,
+ zoomIn= 0xF00CB,
+ zoomOut= 0xF00CC,
+ zoomDefault= 0xF00CD,
+ share= 0xF00CE,
+ save= 0xF00CF,
+ fastForward= 0xF00D0,
+ rewind= 0xF00D1,
+ nextSong= 0xF00D2,
+ previousSong= 0xF00D3,
+ cycle= 0xF00D4,
+ cycleOne= 0xF00D5,
+ cycleOff= 0xF00D6,
+ random= 0xF00D7,
+ randomOff= 0xF00D8,
+ randomAuto= 0xF00D9,
+ closedCaption= 0xF00DA,
+ fullBeat= 0xF00DB,
+ rewind10= 0xF00DC,
+ rewind30= 0xF00DD,
+ foward10= 0xF00DE,
+ foward30= 0xF00DF,
+ fontUp= 0xF00E0,
+ fontDown= 0xF00E1,
+ erase= 0xF00E2,
+ auto= 0xF00E3,
+ },
+ key={-- F0100 - F017F
+ macCmd= 0xF0100,
+ macOpt= 0xF0101,
+ macCtrl= 0xF0102,
+ shift= 0xF0103,
+ capsLock= 0xF0104,
+ enter_or_return= 0xF0105,
+ backspace= 0xF0106,
+ clear= 0xF0107,
+ macFowardDel= 0xF0108,
+ macEsc= 0xF0109,
+ macTab= 0xF010A,
+ fn= 0xF010B,
+ macHome= 0xF010C,
+ macEnd= 0xF010D,
+ macPgup= 0xF010E,
+ macPgdn= 0xF010F,
+ macEnter= 0xF0110,
+ space= 0xF0111,
+ windows= 0xF0112,
+ alt= 0xF0113,
+ ctrl= 0xF0114,
+ winMenu= 0xF0115,
+ tab= 0xF0116,
+ esc= 0xF0117,
+ up= 0xF0118,
+ down= 0xF0119,
+ left= 0xF011A,
+ right= 0xF011B,
+ del= 0xF011C,
+ enterText= 0xF011D,
+ keyboard= 0xF011E,
+ macMediaEject= 0xF011F,
+ isoCtrl= 0xF0120,
+ isoAlt= 0xF0121,
+ macHomeAlt= 0xF0122,
+ macEndAlt= 0xF0123,
+ macPgupAlt= 0xF0124,
+ macPgdnAlt= 0xF0125,
+ iecPower= 0xF0126,
+ },
+ controller={-- F0180 - F01FF
+ xbox= 0xF0180,
+ lt= 0xF0181,
+ rt= 0xF0182,
+ lb= 0xF0183,
+ rb= 0xF0184,
+ xboxX= 0xF0185,
+ xboxY= 0xF0186,
+ xboxA= 0xF0187,
+ xboxB= 0xF0188,
+ joystickL= 0xF0189,
+ joystickR= 0xF018A,
+ jsLU= 0xF018B,
+ jsLD= 0xF018C,
+ jsLR= 0xF018D,
+ jsLL= 0xF018E,
+ jsRU= 0xF018F,
+ jsRD= 0xF0190,
+ jsRR= 0xF0191,
+ jsRL= 0xF0192,
+ jsLPress= 0xF0193,
+ jsRPress= 0xF0194,
+ dpad= 0xF0195,
+ dpadU= 0xF0196,
+ dpadD= 0xF0197,
+ dpadL= 0xF0198,
+ dpadR= 0xF0199,
+ xboxView= 0xF019A,
+ xboxMenu= 0xF019B,
+ xboxShare= 0xF019C,
+ xboxConnect= 0xF019D,
+ ps= 0xF019E,
+ psTriangle= 0xF019F,
+ psCircle= 0xF01A0,
+ psCross= 0xF01A1,
+ psSquare= 0xF01A2,
+ psMute= 0xF01A3,
+ psCreate= 0xF01A4,
+ psOption= 0xF01A5,
+ },
+ mahjong={-- F0200 - F027F
+ m1= 0xF0200,
+ m2= 0xF0201,
+ m3= 0xF0202,
+ m4= 0xF0203,
+ m5= 0xF0204,
+ m6= 0xF0205,
+ m7= 0xF0206,
+ m8= 0xF0207,
+ m9= 0xF0208,
+ s1= 0xF0209,
+ s2= 0xF020A,
+ s3= 0xF020B,
+ s4= 0xF020C,
+ s5= 0xF020D,
+ s6= 0xF020E,
+ s7= 0xF020F,
+ s8= 0xF0210,
+ s9= 0xF0211,
+ p1= 0xF0212,
+ p2= 0xF0213,
+ p3= 0xF0214,
+ p4= 0xF0215,
+ p5= 0xF0216,
+ p6= 0xF0217,
+ p7= 0xF0218,
+ p8= 0xF0219,
+ p9= 0xF021A,
+ ton= 0xF021B,
+ nan= 0xF021C,
+ sha= 0xF021D,
+ pe= 0xF021E,
+ chun= 0xF021F,
+ hatsu= 0xF0220,
+ haku= 0xF0221,
+ hatsuAlt= 0xF0222,
+ hakuAlt= 0xF0223,
+ haru= 0xF0224,
+ natsu= 0xF0225,
+ aki= 0xF0226,
+ fuyu= 0xF0227,
+ ume= 0xF0228,
+ ran= 0xF0229,
+ kiku= 0xF022A,
+ take= 0xF022B,
+ m5Red= 0xF022C,
+ s5Red= 0xF022D,
+ p5Red= 0xF022E,
+ m1Base= 0xF022F,
+ m2Base= 0xF0230,
+ m3Base= 0xF0231,
+ m4Base= 0xF0232,
+ m5Base= 0xF0233,
+ m6Base= 0xF0234,
+ m7Base= 0xF0235,
+ m8Base= 0xF0236,
+ m9Base= 0xF0237,
+ mComb= 0xF0238,
+ s1Base= 0xF0239,
+ s1Comb= 0xF023A,
+ s5Base= 0xF023B,
+ s5Comb= 0xF023C,
+ s7Base= 0xF023D,
+ s7Comb= 0xF023E,
+ s9Base= 0xF023F,
+ s9Comb= 0xF0240,
+ p2Base= 0xF0241,
+ p2Comb= 0xF0242,
+ p3Base= 0xF0243,
+ p3Comb1= 0xF0244,
+ p3Comb2= 0xF0245,
+ p4Base= 0xF0246,
+ p4Comb= 0xF0247,
+ p5Base= 0xF0248,
+ p5Comb1= 0xF0249,
+ p5Comb2= 0xF024A,
+ p6Base= 0xF024B,
+ p6Comb= 0xF024C,
+ p7Base= 0xF024D,
+ p7Comb= 0xF024E,
+ p9Base= 0xF024F,
+ p9Comb1= 0xF0250,
+ p9Comb2= 0xF0251,
+ frameComb= 0xF0252,
+ s1j= 0xF0253,
+ s1jBase= 0xF0254,
+ s1jComb= 0xF0255,
+ },
+ cards={-- F0300 - F070F
+ cardBack= 0xF0300,
+ clubA= 0xF0301,
+ club2= 0xF0302,
+ club3= 0xF0303,
+ club4= 0xF0304,
+ club5= 0xF0305,
+ club6= 0xF0306,
+ club7= 0xF0307,
+ club8= 0xF0308,
+ club9= 0xF0309,
+ club10= 0xF030A,
+ clubJ= 0xF030B,
+ clubC= 0xF030C,
+ clubQ= 0xF030D,
+ clubK= 0xF030E,
+ heartA= 0xF0401,
+ heart2= 0xF0402,
+ heart3= 0xF0403,
+ heart4= 0xF0404,
+ heart5= 0xF0405,
+ heart6= 0xF0406,
+ heart7= 0xF0407,
+ heart8= 0xF0408,
+ heart9= 0xF0409,
+ heart10= 0xF040A,
+ heartJ= 0xF040B,
+ heartC= 0xF040C,
+ heartQ= 0xF040D,
+ heartK= 0xF040E,
+ diamondA= 0xF0501,
+ diamond2= 0xF0502,
+ diamond3= 0xF0503,
+ diamond4= 0xF0504,
+ diamond5= 0xF0505,
+ diamond6= 0xF0506,
+ diamond7= 0xF0507,
+ diamond8= 0xF0508,
+ diamond9= 0xF0509,
+ diamond10= 0xF050A,
+ diamondJ= 0xF050B,
+ diamondC= 0xF050C,
+ diamondQ= 0xF050D,
+ diamondK= 0xF050E,
+ jokerBlack= 0xF050F,
+ clubA= 0xF0601,
+ club2= 0xF0602,
+ club3= 0xF0603,
+ club4= 0xF0604,
+ club5= 0xF0605,
+ club6= 0xF0606,
+ club7= 0xF0607,
+ club8= 0xF0608,
+ club9= 0xF0609,
+ club10= 0xF060A,
+ clubJ= 0xF060B,
+ clubC= 0xF060C,
+ clubQ= 0xF060D,
+ clubK= 0xF060E,
+ jokerWhite= 0xF060F,
+ }
+}
+
+for _,pack in next,L do
+ for name,unicode in next,pack do
+ pack[name]=utf8_convert(unicode)
+ end
+end
+
+return L
\ No newline at end of file
diff --git a/conf.lua b/conf.lua
new file mode 100644
index 0000000..543b012
--- /dev/null
+++ b/conf.lua
@@ -0,0 +1,13 @@
+function love.conf(t)
+ t.identity = "tromi_mobile"
+ t.externalstorage=true
+
+ t.console = true
+
+ t.window.title = "Tromi"
+ t.window.width = 1280
+ t.window.height = 960
+ t.window.vsync = false
+
+ t.accelerometerjoystick = true
+end
diff --git a/funcs.lua b/funcs.lua
new file mode 100644
index 0000000..8460c72
--- /dev/null
+++ b/funcs.lua
@@ -0,0 +1,191 @@
+function copy(t)
+ -- returns deep copy of t (as opposed to the shallow copy you get from var = t)
+ if type(t) ~= "table" then return t end
+ local meta = getmetatable(t)
+ local target = {}
+ for k, v in pairs(t) do target[k] = v end
+ setmetatable(target, meta)
+ return target
+end
+
+function strTrueValues(tbl)
+ -- returns a concatenation of all the keys in tbl with value true, separated with spaces
+ str = ""
+ for k, v in pairs(tbl) do
+ if v == true then
+ str = str .. k .. " "
+ end
+ end
+ return str
+end
+
+function frameTime(min, sec, hth)
+ -- returns a time in frames from a time in minutes-seconds-hundredths format
+ if min == nil then min = 0 end
+ if sec == nil then sec = 0 end
+ if hth == nil then hth = 0 end
+ return min*3600 + sec*60 + math.ceil(hth * 0.6)
+end
+
+function vAdd(v1, v2)
+ -- returns the sum of vectors v1 and v2
+ return {
+ x = v1.x + v2.x,
+ y = v1.y + v2.y
+ }
+end
+
+function vNeg(v)
+ -- returns the opposite of vector v
+ return {
+ x = -v.x,
+ y = -v.y
+ }
+end
+
+function formatTime(frames)
+ -- returns a mm:ss:hh (h=hundredths) representation of the time in frames given
+ if frames < 0 then return formatTime(0) end
+ local min, sec, hund
+ min = math.floor(frames/3600)
+ sec = math.floor(frames/60) % 60
+ hund = math.floor(frames/.6) % 100
+ str = string.format("%02d:%02d.%02d", min, sec, hund)
+ return str
+end
+
+function formatBigNum(number)
+ -- returns a string representing a number with commas as thousands separator (e.g. 12,345,678)
+ local s
+ if type(number) == "number" then
+ s = string.format("%d", number)
+ elseif type(number) == "string" then
+ if not tonumber(number) then
+ return
+ else
+ s = number
+ end
+ else
+ return
+ end
+ local pos = math.mod1(string.len(s), 3)
+ return string.sub(s, 1, pos)
+ .. string.gsub(string.sub(s, pos+1), "(...)", ",%1")
+end
+
+function math.clamp(x, min, max)
+ if max < min then
+ min, max = max, min
+ end
+ return x < min and min or (x > max and max or x)
+end
+
+-- Returns a number congruent to n modulo m in the range [1;m] (as opposed to [0;m-1])
+function math.mod1(n, m)
+ return ((n-1) % m) + 1
+end
+
+---Round a number with specified unit
+---@param n number
+---@param u? number|1
+---@return number
+function math.roundUnit(n,u)
+ local u = u or 1
+ return math.floor(n/u+.5)*u
+end
+
+---@param t1 table
+---@param t2 table
+---@return table t
+---Merge 2 tables into one
(**WARNING**: t2 can **overwrite** some value of t1 if both tables have some same keys!)
+function table.merge(t1,t2)
+ local t = {}
+ for k, v in pairs(t1) do t[k] = v end
+ for k, v in pairs(t2) do t[k] = v end
+ return t
+end
+
+function table.contains(table, element)
+ for _, value in pairs(table) do
+ if value == element then
+ return true
+ end
+ end
+ return false
+end
+
+function table.highest(table)
+ local i = 1
+ local highest = nil
+ for key, value in pairs(table) do
+ if highest == nil then highest = key end
+ if table[key] > table[highest] then highest = key end
+ end
+ return highest
+end
+
+function table.lowest(table)
+ local i = 1
+ local lowest = nil
+ for key, value in pairs(table) do
+ if lowest == nil then lowest = key end
+ if table[key] < table[lowest] then lowest = key end
+ end
+ return lowest
+end
+
+function drawText(text, x, y, size, orientation, color)
+ if color == nil then color = {1, 1, 1, 1} end
+ love.graphics.setFont(FONT_tromi)
+ love.graphics.setColor(0, 0, 0, 0.8)
+ love.graphics.printf(text, x+1, y+1, size*2, orientation, nil, 0.50)
+ love.graphics.setColor(color)
+ love.graphics.printf(text, x, y, size*2, orientation, nil, 0.5)
+end
+
+function drawBoldText(text, x, y, size, orientation, color)
+ if color == nil then color = {1, 1, 1, 1} end
+ love.graphics.setFont(FONT_bold)
+ love.graphics.setColor(0, 0, 0, 0.8)
+ love.graphics.printf(text, x+1, y+1, size*2, orientation, nil, 0.50)
+ love.graphics.setColor(color)
+ love.graphics.printf(text, x, y, size*2, orientation, nil, 0.5)
+end
+
+function drawBigText(text, x, y, size, orientation, color)
+ if color == nil then color = {1, 1, 1, 1} end
+ love.graphics.setFont(FONT_big)
+ love.graphics.setColor(0, 0, 0, 0.8)
+ love.graphics.printf(text, x+1, y+1, size*2, orientation, nil, 0.50)
+ love.graphics.setColor(color)
+ love.graphics.printf(text, x, y, size*2, orientation, nil, 0.5)
+end
+
+function boolToInt(value)
+ if value then return 1 else return 0 end
+end
+
+function pairsByKeysReverse (t, f)
+ local a = {}
+ for n in pairs(t) do table.insert(a, n) end
+ table.sort(a, f)
+ local i = #t+1 -- iterator variable
+ local iter = function () -- iterator function
+ i = i - 1
+ if a[i] == nil then return nil
+ else return a[i], t[a[i]]
+ end
+ end
+ return iter
+end
+
+function split_string(inputstr, sep)
+ if sep == nil then
+ sep = "%s"
+ end
+ local t={}
+ for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
+ table.insert(t, str)
+ end
+ return t
+end
diff --git a/game/gamemode.lua b/game/gamemode.lua
new file mode 100644
index 0000000..96de685
--- /dev/null
+++ b/game/gamemode.lua
@@ -0,0 +1,1120 @@
+local Object = require 'libs.classic'
+local bit = require("bit")
+local lualzw = require 'libs.lualzw'
+local bitser = require 'libs.bitser'
+
+local playedReadySE = false
+
+local Grid = require 'game.grid'
+local Randomizer = require 'game.randomizer'
+
+local GameMode = Object:extend()
+
+function GameMode:new(player_name, input_file, replay_grade)
+ VCTRL.toggle(MOBILE and not input_file and not SETTINGS.tvMode)
+
+ if player_name == nil then self.training = true else self.training = false end
+ if input_file ~= nil then
+ input_file = love.filesystem.read(REPLAY_DIR..input_file)
+ input_file = lualzw.decompress(input_file)
+ local seed = self:getInputPieceSeq(input_file)
+ self.replay_inputs = self:getReplayInputs(input_file)
+ self.randomizer = Randomizer(false, tonumber(seed))
+ self.input_playback = true
+ self.grade = replay_grade
+ self.frames = 1
+ elseif self.training then
+ player_name = 'TRN'
+ replay_grade = 'N/A'
+ self.randomizer = Randomizer(false, nil)
+ self.input_playback = false
+ self.frames = 0
+ else
+ self.randomizer = Randomizer(false, nil)
+ self.input_playback = false
+ self.frames = 0
+ end
+ self.player_name = player_name
+ self.grid = Grid(10, 20)
+ self.piece = nil
+ self.ready_frames = 100
+ self.game_over_frames = 0
+ self.are = 0
+ self.lcd = 0
+ self.das = { direction = "none", frames = -1 }
+ self.move = "none"
+ self.prev_inputs = {}
+ self.next_queue = {}
+ self.game_over = false
+ self.clear = false
+ self.completed = false
+ self.next_queue_length = 1
+ self.irs = true
+ self.drop_locked = false
+ self.hard_drop_locked = false
+ self.cleared_block_table = {}
+ self.last_lcd = 0
+ self.grade_score = 0
+ self.did_grades = false
+ self.active_frames = 0
+ self.total_lines = 0
+ self.lineClearPoints = {[0]=0, 0, 1667, 3750, 6668, 8335}
+ self.gradeNames = {
+ "19k", "18k", "17k", "16k", "15k", "14k", "13k", "12k", "11k", "10k",
+ "9k", "8k", "7k", "6k", "5k", "4k", "3k", "2k", "1k",
+ "1D", "2D", "3D", "4D", "5D", "6D", "7D", "8D", "9D"
+ }
+ self.promo_table = {
+ 26666, 53333, 79999, 106666, 133333, 159999, 186666, 213333, 239999, 266666,
+ 293333, 319999, 346666, 373333, 399999, 426666, 453333, 479999, 506666,
+ 533333, 559999, 586666, 613333, 639999, 666666, 693333, 719999, 719999
+ }
+ self.autopromo_table = {
+ 79999, 106666, 133333, 159999, 186666, 213333, 239999, 266666, 293333, 319999,
+ 346666, 373333, 399999, 426666, 453333, 479999, 506666, 533333, 559999,
+ 586666, 613333, 639999, 666666, 693333, 719999, 746666, 773333, 1000000
+ }
+ self.demo_table = {
+ -1, 13332, 25000, 40000, 50000, 60000, 60000, 120000, 120000, 120000,
+ 180000, 180000, 240000, 240000, 300000, 300000, 360000, 360000, 360000,
+ 420000, 420000, 480000, 480000, 480000, 480000, 540000, 540000, 540000
+ }
+ self.speed_divisor = 10000
+ self.line_clear_flash = 0
+ self.lines_cleared = 0
+ SOUNDS['bgm_firsthalf']:setVolume(0.3)
+ SOUNDS['bgm_secondhalf']:setVolume(0.35)
+ self.audio_stopped = false
+ self.recorded_inputs = {}
+ self.input_saved = false
+ self.end_grid_clear = false
+ self.last_active = 0
+ self.move_count = 0
+ self.target = 0
+ self.last_percent = 0
+ self.total_speed_loss = 0
+ self.grade_change_flash = 1
+ self.grade_change_color = {1,1,1,1}
+ self.point_flash = 0
+ self.point_flash_color = {1,1,1,1}
+ self.end_game_sound = nil
+ self.speed_level = 0
+ self.last_level = 0
+ self.nextbgmflag = false
+ self.firstbgmlooped = false
+ self.last_holes = 0
+ self.last_meter = 0
+ self.speed_table = {}
+ self.holes_bonus_freeze = 0
+ self.holes_bonus = 0
+ self.last_speed = 0
+ self.holes_bonus_lines = 0
+ self.bonus_components = {speed=1666,clean=750}
+ self.score_totals = {clean=0,speed=0,lines=0}
+ self.score_percents = {clean=0,speed=0,lines=0}
+ self.directions_pressed = {}
+ self.lines_bonus = 0
+ self.moved = false
+ self.midspeed_flash = false
+ self.lastdir = 1
+ self.spin_rng = {0,0,0,0,0,0,0,0,0,0}
+ self.lastlock = 0
+ self.up_lock = false
+ -- gravity, are, lock, das
+ self.delay_table = {
+ [0]={0.013, 20, 30, 12},{0.015, 20, 30, 12},{0.016, 20, 30, 12},{0.018, 20, 30, 12},{0.021, 20, 30, 12},{0.025, 20, 30, 12},
+ {0.027, 20, 30, 12},{0.030, 20, 30, 12},{0.033, 20, 30, 12},{0.038, 20, 30, 12},{0.043, 20, 30, 12},{0.051, 20, 30, 12},
+ {0.056, 20, 30, 12},{0.062, 20, 30, 12},{0.070, 20, 30, 12},{0.079, 20, 30, 12},{0.092, 20, 30, 12},{0.109, 20, 30, 12},
+ {0.120, 20, 30, 12},{0.134, 20, 30, 12},{0.152, 20, 30, 12},{0.175, 20, 30, 12},{0.206, 20, 30, 12},{0.250, 20, 30, 12},
+ {0.281, 20, 30, 12},{0.319, 20, 30, 12},{0.370, 20, 30, 12},{0.441, 20, 30, 12},{0.545, 20, 30, 12},{0.712, 20, 30, 12},
+ {0.842, 20, 30, 12},{1.029, 20, 30, 12},{1.323, 20, 30, 12},{1.852, 20, 30, 11},{3.086, 20, 30, 10},{9.259, 20, 30, 9},
+ {20, 20, 30, 8},{20, 20, 30, 8},{20, 19, 28, 8},{20, 18, 27, 8},{20, 17, 26, 8},{20, 17, 25, 8},
+ {20, 16, 24, 8},{20, 16, 23, 8},{20, 15, 23, 8},{20, 15, 22, 8},{20, 14, 21, 8},{20, 14, 21, 8},
+ {20, 14, 20, 8},{20, 13, 20, 8},{20, 13, 20, 8},{20, 13, 19, 8},{20, 13, 19, 8},{20, 13, 19, 8},
+ {20, 12, 18, 8}
+ }
+ if not self.input_playback and not self.training and not PENTO_MODE then
+ self:readGradeHistory()
+ end
+end
+
+function GameMode:readGradeHistory()
+ if love.filesystem.getInfo(SAVE_DIR..self.player_name.."_grade_history.sav") then
+ self.grade_history = FILE.read(SAVE_DIR..self.player_name.."_grade_history.sav")
+ else
+ self.grade_history = {1,2,0,0}
+ end
+ self.grade = self.grade_history[1]
+ self.starting_grade = self.grade
+ if self.grade > 1 then
+ local temp_grade = copy(self.grade_history); temp_grade[2] = 0
+ FILE.write(SAVE_DIR..self.player_name.."_grade_history.sav", temp_grade)
+ end
+end
+
+function GameMode:readHiScores()
+ if love.filesystem.getInfo(HIscoreFILE) then
+ self.hi_scores = FILE.read(HIscoreFILE)
+ else
+ self.hi_scores = {"TRO",0,"MIT",0,"ROM",0,"ITR",0,"OMI",0}
+ end
+end
+
+function GameMode:updateGradeHistory()
+ if self.grade_score >= self.promo_table[self.grade] then
+ if (self.grade == 28 and self.grade_history[2] < 4) or self.grade < 28 then self.grade_history[2] = self.grade_history[2] + 1 end
+ self.point_flash = 60
+ self.point_flash_color = {0,1,0,1}
+ elseif self.grade_score <= self.demo_table[self.grade] then
+ if (self.grade == 1 and self.grade_history[2] > 0) or self.grade > 1 then self.grade_history[2] = self.grade_history[2] - 1 end
+ self.point_flash = 60
+ self.point_flash_color = {1,0,0,1}
+ end
+ local auto_flag = false
+ while self.grade_score >= self.autopromo_table[self.grade] and self.grade < 28 do
+ self.grade = self.grade + 1
+ self.point_flash = 1
+ self.grade_change_flash = 120
+ self.grade_change_color = {0,0,1,1}
+ self.end_game_sound = "autopromote"
+ auto_flag = true
+ end
+ if self.grade_history[2] >= 5 and self.grade < 28 and auto_flag == false then
+ self.grade = self.grade + 1
+ self.point_flash = 1
+ self.grade_change_flash = 120
+ self.grade_change_color = {0,1,0,1}
+ self.end_game_sound = "promote"
+ elseif self.grade_history[2] < 0 and self.grade > 1 and auto_flag == false then
+ self.grade = self.grade - 1
+ self.point_flash = 1
+ self.grade_change_flash = 120
+ self.grade_change_color = {1,0,0,1}
+ self.end_game_sound = "demote"
+ end
+ if self.starting_grade ~= self.grade then
+ self.grade_history[1] = self.grade
+ self.grade_history[2] = 2
+ end
+ self.grade_history[4] = self.grade_history[4] + 1
+end
+
+function GameMode:updateHiScores()
+ self:readHiScores()
+ local hiscore_pos = {0,0}
+ local i = 2
+ local score_position = -1
+ while i <= 10 do
+ if score_position == -1 and self.hi_scores[i] < self.grade_score then
+ score_position = i
+ end
+ i = i + 2
+ end
+ if score_position ~= -1 then
+ i = 8
+ while i >= score_position-1 do
+ self.hi_scores[i+2] = self.hi_scores[i]
+ i = i - 1
+ end
+ self.hi_scores[score_position-1] = self.player_name:upper()
+ self.hi_scores[score_position] = self.grade_score
+ hiscore_pos = {score_position-1, score_position}
+ end
+ FILE.write(HIscoreFILE, self.hi_scores)
+ return hiscore_pos
+end
+
+function GameMode:getARR() return 1 end
+function GameMode:getDropSpeed() return 1 end
+function GameMode:getARE()
+ if self.training then return 20 end
+ if self.speed_level <= #self.delay_table then return self.delay_table[self.speed_level][2]
+ else return self.delay_table[#self.delay_table][2] end
+end
+
+function GameMode:getLineARE() return self:getARE() end
+
+function GameMode:getLockDelay()
+ if self.training then return 99999999999 end
+ if self.speed_level <= #self.delay_table then return self.delay_table[self.speed_level][3]
+ else return self.delay_table[#self.delay_table][3] end
+end
+
+function GameMode:getLineClearDelay() return self:getARE() end
+
+function GameMode:getDasLimit()
+ if self.training then return 8 end
+ if self.speed_level <= #self.delay_table then return self.delay_table[self.speed_level][4]
+ else return self.delay_table[#self.delay_table][4] end
+end
+function GameMode:getDasCutDelay() return 0 end
+
+function GameMode:getGravity()
+ if self.training then return 20 end
+ if self.speed_level <= #self.delay_table then return self.delay_table[self.speed_level][1]
+ else return self.delay_table[#self.delay_table][1] end
+end
+
+function GameMode:getNextPiece(ruleset)
+ local shape = self.randomizer:nextPiece()
+ return {
+ skin = self:getSkin(),
+ shape = shape,
+ orientation = ruleset:getDefaultOrientation(shape),
+ }
+end
+
+function GameMode:getSkin()
+ return "2tie"
+end
+
+function GameMode:initialize(ruleset)
+ self.ruleset = ruleset
+ for i = 1, math.max(self.next_queue_length, 1) do
+ table.insert(self.next_queue, self:getNextPiece(ruleset))
+ end
+end
+
+function GameMode:saveInputs()
+ local input_string = 'NEW'..string.format("%16d", self.randomizer.seed)..';'
+ for frame, input_set in pairs(self.recorded_inputs) do
+ input_string = input_string..string.format('%03d', tostring(input_set))
+ end
+ local inputfile = love.filesystem.newFile(REPLAY_DIR..tostring(os.time()).."_"..self.player_name.."_"..self.starting_grade.."_"..self.grade_score.."_replay"..'.sav', 'w')
+ inputfile:write(lualzw.compress(input_string))
+ inputfile:close()
+end
+
+local GAME_input_code_list = {
+ right = 1 ,
+ rotate_right2 = 2 ,
+ up = 4 ,
+ rotate_left = 8 ,
+ left = 16 ,
+ down = 32 ,
+ rotate_right = 64 ,
+ rotate_left2 = 128,
+}
+function GameMode:storeInput(input_set)
+ local current_frame_input = 0
+ for key, code in pairs(GAME_input_code_list) do
+ if input_set[key] then
+ current_frame_input = bit.bor(current_frame_input, code)
+ end
+ end
+ self.recorded_inputs[self.frames] = current_frame_input
+end
+function GameMode:getReplayInputs(input_file)
+ local replay_inputs = {}
+ local semicolon_position = string.find(input_file, ';', 1, true)
+
+ if semicolon_position then
+ for i = semicolon_position + 1, #input_file, 3 do
+ local input_list = {right = false, rotate_right2 = false, up = false, rotate_left = false, left = false, down = false, rotate_right = false, rotate_left2 = false}
+ local coded_input = tonumber(string.sub(input_file, i, i+2))
+
+ for key, code in pairs(GAME_input_code_list) do
+ ---@diagnostic disable-next-line: param-type-mismatch
+ if bit.band(coded_input, code) == code then input_list[key] = true end
+ end
+
+ table.insert(replay_inputs, input_list)
+ end
+ end
+ return replay_inputs
+end
+
+function GameMode:getInputPieceSeq(input_file)
+ local string_start
+ if string.sub(input_file, 1, 3) == 'NEW' then
+ string_start = 4
+ self.old_replay = false
+ else
+ string_start = 1
+ self.old_replay = true
+ end
+ return string.sub(
+ input_file, string_start,
+ string.find(input_file, ';', string_start, true) - 1
+ ) -- seed
+end
+
+function GameMode:update(inputs, ruleset)
+ if self.input_playback and self.replay_inputs[self.frames] ~= nil then
+ inputs = self.replay_inputs[self.frames]
+ end
+ if self.grade_change_flash > 0 and self.game_over_frames >= 2 then self.grade_change_flash = self.grade_change_flash - 1 end
+ if self.point_flash > 0 and self.game_over_frames >= 2 then self.point_flash = self.point_flash - 1 end
+ if self.game_over_frames == 2 then
+ PlaySEOnce(self.end_game_sound)
+ end
+ if self.game_over or self.completed then
+ self.game_over_frames = self.game_over_frames + 1
+ if self.game_over_frames == 1 then self:storeInput(inputs) end
+ if self.lcd > 0 then
+ self.lcd = self.lcd - 1
+ elseif self.game_over_frames == 30 then
+ local cleared_row_count = self.grid:getClearedRowCount()
+ self.grid:clearClearedRows()
+ self:afterLineClear(cleared_row_count)
+ elseif not self.input_playback and not self.input_saved and not self.training and not PENTO_MODE and self.game_over_frames == 50 then
+ self:saveInputs()
+ self.input_saved = true
+ end
+ return
+ end
+ if inputs["up"] and self.training and not self.up_lock then
+ table.remove(self.next_queue, 1)
+ table.insert(self.next_queue, self:getNextPiece(ruleset))
+ self.up_lock = true
+ end
+ if not inputs["up"] then self.up_lock = false end
+ local dir_list = {"down", "left", "right"}
+ for i = 1, #dir_list do
+ if inputs[dir_list[i]] and not table.contains(self.directions_pressed, dir_list[i]) then
+ table.insert(self.directions_pressed, dir_list[i])
+ elseif not inputs[dir_list[i]] then
+ for j=1, #self.directions_pressed do
+ if self.directions_pressed[j] == dir_list[i] then table.remove(self.directions_pressed, j) end
+ end
+ end
+ end
+ if #self.directions_pressed > 0 then
+ for i=1, #self.directions_pressed-1 do
+ inputs[self.directions_pressed[i]] = false
+ end
+ inputs[self.directions_pressed[#self.directions_pressed]] = true
+ if inputs['left'] then self.lastdir = -1
+ elseif inputs['right'] then self.lastdir = 1 end
+ end
+
+ -- advance one frame
+
+ if self:advanceOneFrame(inputs, ruleset) == false then return end
+
+ self:chargeDAS(inputs, self:getDasLimit(), self:getARR())
+
+ -- set attempt flags
+ if inputs["left"] or inputs["right"] then self:onAttemptPieceMove(self.piece, self.grid) end
+ if (
+ inputs["rotate_left"] or inputs["rotate_right"] or
+ inputs["rotate_left2"] or inputs["rotate_right2"]
+ ) then
+ self:onAttemptPieceRotate(self.piece, self.grid)
+ end
+
+ if self.piece == nil then
+ self:processDelays(inputs, ruleset)
+ else
+ self:whilePieceActive()
+ local gravity = self:getGravity()
+
+ -- diff vars to use in checks
+ local piece_y = self.piece.position.y
+ local piece_x = self.piece.position.x
+ local piece_rot = self.piece.rotation
+ ruleset:processPiece(
+ inputs, self.piece, self.grid, gravity, self.prev_inputs, self.move,
+ self:getLockDelay(), self:getDropSpeed(),
+ self.drop_locked, self.hard_drop_locked,
+ false, false, false, self.lastdir
+ )
+
+ local piece_dy = self.piece.position.y - piece_y
+ local piece_dx = self.piece.position.x - piece_x
+ local piece_drot = self.piece.rotation - piece_rot
+
+ -- das cut
+ if (
+ (piece_dy ~= 0 and (inputs.up or inputs.down)) or
+ (piece_drot ~= 0 and (
+ inputs.rotate_left or inputs.rotate_right or
+ inputs.rotate_left2 or inputs.rotate_right2
+ ))
+ ) then
+ self:dasCut()
+ end
+
+ if (piece_dx ~= 0) then
+ self.piece.last_rotated = false
+ self:onPieceMove(self.piece, self.grid, piece_dx)
+ end
+ if (piece_dy ~= 0) then
+ self.piece.last_rotated = false
+ self:onPieceDrop(self.piece, self.grid, piece_dy)
+ end
+ if (piece_drot ~= 0) then
+ self.piece.last_rotated = true
+ self:onPieceRotate(self.piece, self.grid, piece_drot)
+ end
+
+ if inputs["down"] == true then
+ if self.piece:isDropBlocked(self.grid) and not self.drop_locked then
+ self.lastlock = self.piece.lock_delay
+ self.piece.locked = true
+ self.piece_soft_locked = true
+ end
+ end
+
+ if self.piece.locked == true then
+ local last_x = self.piece.position.x
+ self.grid:applyPiece(self.piece)
+
+ local cleared_row_count = self.grid:getClearedRowCount()
+ self:onPieceLock(self.piece, cleared_row_count)
+ if not self.training then self:updateScore(cleared_row_count) end
+
+ self.cleared_block_table = self.grid:markClearedRows()
+ self.piece = nil
+
+ if cleared_row_count > 0 then
+ for i=1, #self.spin_rng do
+ local spin = math.abs(i-(last_x+3))
+ --if spin < 0 then spin = 0 end
+ self.spin_rng[i] = spin
+ end
+ PlaySE("erase")
+ self.lcd = self:getLineClearDelay()
+ self.last_lcd = self.lcd
+ self.are = self:getLineARE()
+ if self.lcd == 0 then
+ self.grid:clearClearedRows()
+ self:afterLineClear(cleared_row_count)
+ if self.are == 0 then
+ self:initializeOrHold(inputs, ruleset)
+ end
+ end
+ self:onLineClear(cleared_row_count)
+ else
+ self.are = self:getARE()
+ end
+
+ end
+ end
+ self.moved = false
+ if not self.input_playback and not self.game_over and not self.completed and not self.training and not PENTO_MODE then self:storeInput(inputs) end
+ self.prev_inputs = inputs
+end
+
+function GameMode:detectHoles(x, y)
+ if y < 1 then return true end -- reached the top, no hole
+ local cell = self.grid:getCell(x, y)
+ if table.contains(self.visited, x..'.'..y) then
+ return false
+ else
+ table.insert(self.visited, x..'.'..y)
+ end
+ if cell.colour ~= "" or cell.oob then return false end
+ if self:detectHoles(x+1, y) then return true end
+ if self:detectHoles(x-1, y) then return true end
+ if self:detectHoles(x, y+1) then return true end
+ if self:detectHoles(x, y-1) then return true end
+ return false
+end
+
+function GameMode:stackQualityCheck()
+ local hole_num = 0
+ self.visited = {}
+ for x=1, 10 do
+ for y = 1, 20 do
+ if self.grid:getCell(x, y).colour == "" and not table.contains(self.visited, x..'.'..y) then
+ local stack_clean = self:detectHoles(x, y)
+ if not stack_clean then
+ hole_num = hole_num + 1
+ end
+ end
+ end
+ end
+ return hole_num
+end
+
+function GameMode:doStackQuality()
+ local contiguous_holes = self:stackQualityCheck()
+ local total_speed = 0
+ if contiguous_holes > self.last_holes then
+ self.speed_table[1] = 0
+ self.last_percent = 0
+ end
+ for i=1, #self.speed_table do
+ total_speed = total_speed + self.speed_table[i]
+ end
+ if total_speed <= 0 then total_speed = 0.001 end
+ self.total_speed_loss = total_speed / #self.speed_table
+ if #self.speed_table == 0 then self.total_speed_loss = 0 end
+ self.last_holes = contiguous_holes
+end
+
+function GameMode:doSpeedCheck()
+ self.target = self.move_count
+ local speed = (self.target/self.active_frames)
+ if speed > 1 then speed = 1 end
+ table.insert(self.speed_table, 1, speed)
+ while #self.speed_table > 50 do
+ table.remove(self.speed_table, #self.speed_table)
+ end
+ local total_speed = 0
+ for i=1, #self.speed_table do
+ total_speed = total_speed + self.speed_table[i]
+ end
+ if total_speed <= 0 then total_speed = 0.001 end
+ self.total_speed_loss = total_speed / #self.speed_table
+ if #self.speed_table == 0 then self.total_speed_loss = 0 end
+ self.last_active = self.active_frames
+ self.last_percent = speed
+ self.active_frames = 0
+ self.move_count = 0
+end
+
+function GameMode:updateScore(cleared_lines)
+ self:doSpeedCheck()
+ if cleared_lines >= 1 then
+ while cleared_lines+self.total_lines > 300 do
+ cleared_lines = cleared_lines - 1
+ end
+ self.last_speed = math.ceil(self.total_speed_loss * self.bonus_components['speed'] * cleared_lines)
+ self.line_clear_flash = 240
+ self.lines_cleared = cleared_lines
+ self.score_to_add = self.lineClearPoints[cleared_lines] + self.last_speed
+ self.grade_score = self.grade_score + self.score_to_add
+ self.last_meter = self.holes_bonus + self.last_speed
+ self.speed_level = math.floor(self.grade_score / self.speed_divisor)
+ self.score_totals['speed'] = self.score_totals['speed'] + self.last_speed
+ self.score_totals['lines'] = self.score_totals['lines'] + self.lineClearPoints[cleared_lines]
+ end
+end
+
+function GameMode:advanceOneFrame()
+ if self.ready_frames ~= 0 and not self.audio_stopped and not self.training then
+ if SOUNDS['bgm_title']:getVolume() > 0.01 then SOUNDS['bgm_title']:setVolume(SOUNDS['bgm_title']:getVolume()-0.01) end
+ if self.ready_frames == 5 then
+ love.audio.stop()
+ self.audio_stopped = true
+ end
+ end
+ if self.training and not SOUNDS['bgm_title']:isPlaying() and SETTINGS["music"] then SOUNDS['bgm_title']:play() end
+ if not self.training then
+ if self.nextbgmflag and SOUNDS['bgm_firsthalf']:isPlaying() then
+ if SOUNDS['bgm_firsthalf']:getVolume() > 0.1 then
+ SOUNDS['bgm_firsthalf']:setVolume(SOUNDS['bgm_firsthalf']:getVolume()-0.01)
+ else
+ SOUNDS['bgm_firsthalf']:stop()
+ end
+ end
+ if self.ready_frames < 1 and not SOUNDS['bgm_firsthalf']:isPlaying() and not SOUNDS['bgm_secondhalf']:isPlaying() and SETTINGS["music"] then
+ if not self.nextbgmflag then SOUNDS['bgm_firsthalf']:play()
+ elseif self.total_lines < 296 then SOUNDS['bgm_secondhalf']:play() end
+ end
+ if self.total_lines >= 296 then
+ SOUNDS['bgm_firsthalf']:stop()
+ SOUNDS['bgm_secondhalf']:stop()
+ end
+ end
+ if self.clear then
+ self.completed = true
+ end
+ self.frames = self.frames + 1
+ if self.input_playback == true and self.old_replay == true and self.ready_frames ~= 0 then
+ self.frames = 0
+ end
+ if self.line_clear_flash > 0 then self.line_clear_flash = self.line_clear_flash - 1 end
+ if self.line_clear_flash == 0 then
+ self.lines_cleared = 0
+ self.score_to_add = 0
+ self.last_meter = 0
+ self.holes_bonus = 0
+ self.last_speed = 0
+ self.holes_bonus_lines = 0
+ self.lines_bonus_lines = 0
+ self.lines_bonus = 0
+ end
+ return true
+end
+
+-- event functions
+function GameMode:whilePieceActive()
+ self.active_frames = self.active_frames + 1
+end
+function GameMode:onAttemptPieceMove(piece, grid) end
+function GameMode:onAttemptPieceRotate(piece, grid) end
+function GameMode:onPieceMove(piece, grid, dx)
+ if not self.moved then
+ self.move_count = self.move_count + 1
+ self.moved = true
+ end
+end
+function GameMode:onPieceRotate(piece, grid, drot)
+ if not self.moved then
+ self.move_count = self.move_count + 1
+ self.moved = true
+ end
+end
+function GameMode:onPieceDrop(piece, grid, dy)
+ if not self.moved then
+ self.move_count = self.move_count + 1
+ self.moved = true
+ end
+end
+function GameMode:onPieceLock(piece, cleared_row_count)
+ if not self.moved then
+ self.move_count = self.move_count + 1
+ self.moved = true
+ end
+ self.lastdir = 0
+ PlaySE("lock")
+end
+
+function GameMode:onLineClear(cleared_row_count)
+ if not self.training then self.total_lines = math.min(self.total_lines + cleared_row_count, 300) end
+ if self.total_lines == 300 and not self.clear then
+ self.clear = true
+ end
+end
+
+function GameMode:afterLineClear(cleared_row_count) end
+
+function GameMode:onPieceEnter()
+ self:doStackQuality()
+end
+
+function GameMode:onGameOver()
+ if not self.training then VCTRL.toggle(false) end
+ if not self.input_playback and not self.training and not PENTO_MODE then
+ if not self.did_grades then
+ self.grade_score = self.grade_score + self.speed_level
+ if #self.speed_table >= 49 then
+ self:updateGradeHistory()
+ end
+ hiscore_pos = self:updateHiScores()
+ FILE.write(SAVE_DIR..self.player_name.."_grade_history.sav", self.grade_history)
+ self.did_grades = true
+ end
+ self:drawEndScoringInfo()
+ elseif not self.did_grades then
+ self.grade_score = self.grade_score + self.speed_level
+ self.did_grades = true
+ end
+ if PENTO_MODE and not self.training then self:drawEndScoringInfo() end
+end
+
+function GameMode:drawEndScoringInfo()
+ love.graphics.setColor(1, 1, 1, 1)
+ drawText("Score: ", 247, 135, 1000, "left")
+ drawBigText(string.format("%s", self.grade_score), 247, 150, 100, "center")
+ if not PENTO_MODE then
+ drawText("Best scores:", 247, 220, 1000, "left")
+
+ local i = 2
+ while i <= 10 do
+ if i == hiscore_pos[1] or i == hiscore_pos[2] then
+ drawText(self.hi_scores[i-1]..' - '..self.hi_scores[i], 255, 220+(i*10), 1000, "left", {0,1,0,1})
+ else
+ drawText(self.hi_scores[i-1]..' - '..self.hi_scores[i], 255, 220+(i*10), 1000, "left")
+ end
+ i = i + 2
+ end
+ end
+end
+
+function GameMode:onGameComplete()
+ self:onGameOver()
+end
+
+-- DAS functions
+
+function GameMode:startRightDAS()
+ self.move = "right"
+ self.das = { direction = "right", frames = 0 }
+ if self:getDasLimit() == 0 then
+ self:continueDAS()
+ end
+end
+
+function GameMode:startLeftDAS()
+ self.move = "left"
+ self.das = { direction = "left", frames = 0 }
+ if self:getDasLimit() == 0 then
+ self:continueDAS()
+ end
+end
+
+function GameMode:continueDAS()
+ local das_frames = self.das.frames + 1
+ if das_frames >= self:getDasLimit() then
+ if self.das.direction == "left" then
+ self.move = (self:getARR() == 0 and "speed" or "") .. "left"
+ self.das.frames = self:getDasLimit() - self:getARR()
+ elseif self.das.direction == "right" then
+ self.move = (self:getARR() == 0 and "speed" or "") .. "right"
+ self.das.frames = self:getDasLimit() - self:getARR()
+ end
+ else
+ self.move = "none"
+ self.das.frames = das_frames
+ end
+end
+
+function GameMode:stopDAS()
+ self.move = "none"
+ self.das = { direction = "none", frames = -1 }
+end
+
+function GameMode:chargeDAS(inputs)
+ if inputs[self.das.direction] == true then
+ self:continueDAS()
+ elseif inputs["right"] == true then
+ self:startRightDAS()
+ elseif inputs["left"] == true then
+ self:startLeftDAS()
+ else
+ self:stopDAS()
+ end
+end
+
+function GameMode:dasCut()
+ self.das.frames = math.max(
+ self.das.frames - self:getDasCutDelay(),
+ -(self:getDasCutDelay() + 1)
+ )
+end
+
+function GameMode:processDelays(inputs, ruleset, drop_speed)
+ if self.ready_frames == 100 then
+ playedReadySE = false
+ playedGoSE = false
+ end
+ if self.ready_frames > 0 then
+ if not playedReadySE then
+ playedReadySE = true
+ PlaySEOnce("ready")
+ end
+ self.ready_frames = self.ready_frames - 1
+ if self.ready_frames == 0 then
+ self:initializeOrHold(inputs, ruleset)
+ end
+ elseif self.lcd > 0 then
+ self.lcd = self.lcd - 1
+ if self.lcd == 0 then
+ local cleared_row_count = self.grid:getClearedRowCount()
+ self.grid:clearClearedRows()
+ self:afterLineClear(cleared_row_count)
+ PlaySE("fall")
+ if self.are == 0 then
+ self:initializeOrHold(inputs, ruleset)
+ end
+ end
+ elseif self.are > 0 then
+ self.are = self.are - 1
+ if self.are == 0 then
+ self:initializeOrHold(inputs, ruleset)
+ end
+ end
+end
+
+function GameMode:initializeOrHold(inputs, ruleset)
+ self:initializeNextPiece(inputs, ruleset, self.next_queue[1])
+ self:onPieceEnter()
+ if not self.grid:canPlacePiece(self.piece) then
+ self.game_over = true
+ end
+ ruleset:dropPiece(
+ inputs, self.piece, self.grid, self:getGravity(),
+ self:getDropSpeed(), self.drop_locked, self.hard_drop_locked
+ )
+end
+
+function GameMode:initializeNextPiece(inputs, ruleset, piece_data, generate_next_piece)
+ self.piece = ruleset:initializePiece(
+ inputs, piece_data, self.grid, self:getGravity(),
+ self.prev_inputs, self.move,
+ self:getLockDelay(), self:getDropSpeed(),
+ self.drop_locked, self.hard_drop_locked, false,
+ (
+ self.frames == 0 or self:getARE() ~= 0
+ ) and self.irs or false, self.lastdir
+ )
+ self.piece_hard_dropped = false
+ self.piece_soft_locked = false
+ if self.piece:isDropBlocked(self.grid) and
+ self.grid:canPlacePiece(self.piece) then
+ PlaySE("bottom")
+ end
+ if generate_next_piece == nil then
+ table.remove(self.next_queue, 1)
+ table.insert(self.next_queue, self:getNextPiece(ruleset))
+ end
+end
+
+function GameMode:animation(x, y, skin, colour)
+ return {
+ 1, 1, 1,
+ -0.25 + 1.25 * (self.lcd / self.last_lcd),
+ skin, colour,
+ 200 + x * 16, y * 16
+ }
+end
+
+function GameMode:canDrawLCA()
+ return self.lcd > 0
+end
+
+function GameMode:drawLineClearAnimation()
+ local grid_position = {x=200,y=64}
+ for y, row in pairs(self.cleared_block_table) do
+ for x, block in pairs(row) do
+ local real_x = (x * 16) + grid_position['x']
+ local real_y = (y * 16) + grid_position['y']
+ local drift = self.spin_rng[x]*-1
+ local fall_timer = (self:getLineClearDelay()-self.lcd)/2
+ fall_timer = (fall_timer*fall_timer)+drift
+ fade_timer = self.lcd/20
+ love.graphics.setColor(1,1,1,fade_timer)
+ love.graphics.draw(BLOCKS[block.skin][block.colour..'_d'], real_x, real_y+fall_timer)
+ if self.lcd > self:getLineClearDelay() - 5 then
+ love.graphics.setColor(1,1,1,fade_timer*0.3)
+ love.graphics.draw(BLOCKS[block.skin]['W'], real_x, real_y+fall_timer)
+ end
+ end
+ end
+end
+
+function GameMode:drawPiece()
+ if self.piece ~= nil then
+ local b = (
+ 1 - (self.piece.lock_delay / self:getLockDelay())
+ )
+ self.piece:draw(0.50 + 0.50 * b, 0.50 + 0.50 * b, self.grid)
+ end
+end
+
+function GameMode:drawNextQueue(ruleset)
+ local colourscheme
+ function drawPiece(piece, skin, offsets, pos_x, pos_y)
+ for index, offset in pairs(offsets) do
+ local x = offset.x + ruleset:getDrawOffset(piece, rotation).x + ruleset.spawn_positions[piece].x
+ local y = offset.y + ruleset:getDrawOffset(piece, rotation).y + 4.7
+ love.graphics.draw(BLOCKS[skin][COLOUR_SCHEMES.Arika[piece]], pos_x+x*16, pos_y+y*16)
+ end
+ end
+ for i = 1, self.next_queue_length do
+ love.graphics.setColor(1, 1, 1, 1)
+ local highness = -54
+ local next_piece = self.next_queue[i].shape
+ local skin = self.next_queue[i].skin
+ local rotation = self.next_queue[i].orientation
+ drawPiece(next_piece, skin, ruleset.block_offsets[next_piece][rotation], 136+i*80, highness)
+ end
+ return false
+end
+
+function GameMode:getBackground()
+ bg = math.floor(self.speed_level/6)
+ if bg > 9 then bg = 9 end
+ if (self.total_lines >= 130 or self.frames >= 23640) and self.last_level ~= bg then self.nextbgmflag = true end
+ self.last_level = bg
+ return bg
+end
+
+function GameMode:drawGrid()
+ local greyscale = false
+ if self.game_over or self.completed then greyscale = true end
+ self.grid:draw(greyscale, (self.game_over_frames/50))
+end
+
+function GameMode:drawInputDisplay(left, top)
+ if self.replay_inputs[self.frames] ~= nil then
+ drawText("•", left+7.5, top+ 8, 1000, "left")
+ drawText(CHAR.key.down , left+ 5, top+18, 1000, "left",{1-boolToInt(self.replay_inputs[self.frames]['down']),1,1-boolToInt(self.replay_inputs[self.frames]['down']),1})
+ drawText(CHAR.key.left , left- 5, top+ 8, 1000, "left",{1-boolToInt(self.replay_inputs[self.frames]['left']),1,1-boolToInt(self.replay_inputs[self.frames]['left']),1})
+ drawText(CHAR.key.right, left+ 15, top+ 8, 1000, "left",{1-boolToInt(self.replay_inputs[self.frames]['right']),1,1-boolToInt(self.replay_inputs[self.frames]['right']),1})
+ drawText("L", left+ 35, top+ 8, 1000, "left",{1-boolToInt(self.replay_inputs[self.frames]['rotate_left']),1,1-boolToInt(self.replay_inputs[self.frames]['rotate_left']),1})
+ drawText("R", left+ 50, top+ 8, 1000, "left",{1-boolToInt(self.replay_inputs[self.frames]['rotate_right']),1,1-boolToInt(self.replay_inputs[self.frames]['rotate_right']),1})
+ drawText("L", left+ 65, top+ 8, 1000, "left",{1-boolToInt(self.replay_inputs[self.frames]['rotate_left2']),1,1-boolToInt(self.replay_inputs[self.frames]['rotate_left2']),1})
+ drawText("R", left+ 80, top+ 8, 1000, "left",{1-boolToInt(self.replay_inputs[self.frames]['rotate_right2']),1,1-boolToInt(self.replay_inputs[self.frames]['rotate_right2']),1})
+ else
+ drawText("•" , left+7.5, top+ 8, 1000, "left")
+ drawText(CHAR.key.down , left+ 5, top+18, 1000)
+ drawText(CHAR.key.left , left- 5, top+ 8, 1000)
+ drawText(CHAR.key.right, left+ 15, top+ 8, 1000)
+ drawText("L" , left+ 35, top+ 8, 1000, "left")
+ drawText("R" , left+ 50, top+ 8, 1000, "left")
+ drawText("L" , left+ 65, top+ 8, 1000, "left")
+ drawText("R" , left+ 80, top+ 8, 1000, "left")
+ end
+end
+
+function GameMode:drawSpeedStats(left, top)
+ love.graphics.setColor(0,0,0,0.5)
+ love.graphics.rectangle("fill", left+3, top+3, 190, 145, 10, 10)
+ love.graphics.setColor(0.05,0.05,0.05,1)
+ love.graphics.rectangle("fill", left, top, 190, 145, 10, 10)
+ drawText("Efficiency Bonus: ", left+15, top+5, 1000, "left")
+ local lines = self.total_lines
+ if lines == 0 then lines = 1 end
+ if self.move_count == 0 then
+ drawText(string.format(" %4d Num. of Moves", self.target), left+15, top+20, 1000, "left")
+ else
+ drawText(string.format(" %4d Num. of Moves", self.move_count), left+15, top+20, 1000, "left")
+ end
+ drawText(string.format("/ %4d Active Frames", self.last_active), left+15, top+35, 1000, "left")
+ drawText(string.format("= %1.2f\n (0 added for hole)\n %1.2f %dpc Average\nx %s x Lines\n+ %4d", self.last_percent, self.total_speed_loss, #self.speed_table, self.bonus_components['speed'], self.last_speed), left+15, top+50, 1000, "left")
+end
+
+function GameMode:drawLinesStats(left, top)
+ love.graphics.setColor(0,0,0,0.5)
+ love.graphics.rectangle("fill", left+3, top+3, 190, 90, 10, 10)
+ love.graphics.setColor(0.05,0.05,0.05,1)
+ love.graphics.rectangle("fill", left, top, 190, 90, 10, 10)
+ local lines = self.total_lines
+ if lines == 0 then lines = 1 end
+ drawText("Lines Bonus: ", left+15, top+10, 1000, "left")
+ -- drawText(string.format("+ %4d %3d%%", self.lineClearPoints[self.lines_cleared], (self.score_totals['lines']/(self.lineClearPoints[4]*math.ceil(lines/4)))*100), left+15, top+25, 1000, "left")
+ drawText(string.format("2 x Lines = %d\n3 x Lines = %d\n4 x Lines = %d", self.lineClearPoints[2], self.lineClearPoints[3], self.lineClearPoints[4]), left+15, top+25, 1000, "left")
+end
+
+function GameMode:drawScoringInfo()
+ -- Name & Grade
+ love.graphics.setColor(0,0,0,0.5)
+ love.graphics.rectangle("fill", 98, 83, 110, 180, 10, 10)
+ love.graphics.setColor(0.05,0.05,0.05,1)
+ love.graphics.rectangle("fill", 95, 80, 110, 180, 10, 10)
+ if not PENTO_MODE then drawText("Grade:", 100, 128, 1000, "left") end
+ -- Line & Level
+ if self.training or SETTINGS["lines"] then
+ love.graphics.setColor(0,0,0,0.5)
+ love.graphics.rectangle("fill", 241, 407, 116, 40, 5, 5) --lines
+ love.graphics.setColor(0.05,0.05,0.05,1)
+ love.graphics.rectangle("fill", 239, 405, 116, 40, 5, 5) --lines
+ drawText(string.format("Level: %2d\nLines: %3d/300", self:getBackground(), self.total_lines), 249, 408, 1000, "left")
+ end
+ -- REPLAY
+ if self.input_playback then
+ love.graphics.setColor(0,0,0,0.5)
+ love.graphics.rectangle("fill", 68, 270, 140, 190, 10, 10)
+ love.graphics.setColor(0.05,0.05,0.05,1)
+ love.graphics.rectangle("fill", 65, 267, 140, 190, 10, 10)
+ drawBoldText(string.format("REPLAY IN PROGRESS\n\n\n\n\n\n\n\n\n%s", formatTime(self.frames)), 70, 275, 1000, "left")
+ drawBigText(string.format("%s", self.grade), 100, 143, 1000, "left")
+ self:drawInputDisplay(103,185)
+ elseif not PENTO_MODE then
+ if math.mod(self.grade_change_flash, 5) ~= 0 then
+ drawBigText(string.format("%s", self.gradeNames[self.grade]), 100, 143, 1000, "left", self.grade_change_color)
+ else
+ drawBigText(string.format("%s", self.gradeNames[self.grade]), 100, 143, 1000, "left")
+ end
+ local points_text = nil
+ if self.grade == 1 and self.grade_history[2] == 2 then
+ points_text = " |.."
+ elseif self.grade == 1 and self.grade_history[2] == 3 then
+ points_text = " .|."
+ elseif self.grade == 1 and self.grade_history[2] == 4 then
+ points_text = " ..|"
+ elseif self.grade_history[2] == 0 then
+ points_text = "|...."
+ elseif self.grade_history[2] == 1 then
+ points_text = ".|..."
+ elseif self.grade_history[2] == 2 then
+ points_text = "..|.."
+ elseif self.grade_history[2] == 3 then
+ points_text = "...|."
+ elseif self.grade_history[2] == 4 then
+ points_text = "....|"
+ end
+ if self.grade > 1 then points_text = '-'..points_text
+ else points_text = ' '..points_text end
+ if self.grade < 28 then points_text = points_text..'+' end
+ drawText("Promotion\nMeter:", 100, 174, 1000, "left")
+ if self.point_flash > 0 then
+ drawBigText(points_text, 100, 208, 1000, "left", self.point_flash_color)
+ else
+ drawBigText(points_text, 100, 208, 1000, "left")
+ end
+ end
+ if (self.game_over or self.completed) and self.game_over_frames <= 50 and not self.input_playback and not self.training and not PENTO_MODE then
+ drawText("SAVING, PLEASE WAIT", 232, 460, 1000, "left")
+ end
+ drawText("Name:", 100, 83, 1000, "left")
+ drawBigText(self.player_name:upper(), 100, 98, 1000, "left")
+ drawText("Ver. 2", 550, 435, 1000, "left")
+ if self.input_playback then
+ self:drawSpeedStats(385, 99)
+ self:drawLinesStats(385, 251)
+ love.graphics.setColor(0,0,0,0.5)
+ love.graphics.rectangle("fill", 385+3, 348+3, 190, 50, 10, 10)
+ love.graphics.setColor(0.05,0.05,0.05,1)
+ love.graphics.rectangle("fill", 385, 348, 190, 50, 10, 10)
+ drawText(string.format("Added: %d\nScore: %d",self.score_to_add, self.grade_score), 392, 356, 1000, "left")
+ end
+
+
+ if self.clear or self.game_over then
+ SOUNDS['bgm_firsthalf']:stop()
+ SOUNDS['bgm_secondhalf']:stop()
+ end
+end
+
+function GameMode:drawBackground()
+ local bg = self:getBackground()
+ local brightness = 0.9
+ local limit = 4
+ if self.training then bg = 0 end
+ --if self.training then bg = math.mod(math.floor(((self.frames+1)/1200)), 10) end
+ if bg == 0 or bg == 6 or bg == 9 then limit = 5 end
+ if bg == 5 then limit = 4.76 end
+ if not BACKGROUNDS[bg]:isPlaying() then
+ BACKGROUNDS[bg]:play()
+ end
+ if BACKGROUNDS[bg]:tell() >= limit then
+ BACKGROUNDS[bg]:rewind()
+ end
+ if bg == 0 or bg == 8 or bg == 9 or bg == 3 then brightness = 0.7 end
+ love.graphics.setColor(brightness, brightness, brightness, 1)
+ love.graphics.draw(BACKGROUNDS[bg])
+end
+
+function GameMode:drawFrame()
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.line(216,80,216,80+(16*self.grid.height))
+ love.graphics.line(216+(16*self.grid.width),80,216+(16*self.grid.width),80+(16*self.grid.height))
+ love.graphics.line(216,80+(16*self.grid.height),216+(16*self.grid.width),80+(16*self.grid.height))
+ love.graphics.line(216,80,216+(16*self.grid.width),80)
+ love.graphics.setColor(0, 0, 0, 1)
+ love.graphics.rectangle(
+ "fill", 216, 80,
+ 16 * self.grid.width, 16 * (self.grid.height)
+ )
+end
+
+function GameMode:drawReadyGo()
+ -- ready/go graphics
+ love.graphics.setColor(1, 1, 1, 1)
+
+ if self.ready_frames <= 100 and self.ready_frames > 52 then
+ drawBigText("Ready...", 246, 240 - 14, 1000)
+ elseif self.ready_frames <= 50 and self.ready_frames > 2 then
+ drawBigText("Go!", 276, 240 - 14, 1000)
+ end
+end
+
+function GameMode:drawCustom() end
+
+function GameMode:draw(paused)
+ self:drawBackground()
+ self:drawFrame()
+ self:drawGrid()
+ self:drawPiece()
+ self:drawNextQueue(self.ruleset)
+ if not self.training then self:drawScoringInfo() end
+ self:drawReadyGo()
+ self:drawCustom()
+ if self:canDrawLCA() then
+ self:drawLineClearAnimation()
+ end
+
+ if self.completed then
+ self:onGameComplete()
+ elseif self.game_over then
+ self:onGameOver()
+ end
+end
+
+return GameMode
diff --git a/game/grid.lua b/game/grid.lua
new file mode 100644
index 0000000..7b08fdc
--- /dev/null
+++ b/game/grid.lua
@@ -0,0 +1,211 @@
+local Object = require 'libs.classic'
+
+local Grid = Object:extend()
+
+local empty = { skin = "", colour = "", oob=false }
+local oob = { skin = "", colour = "", oob=true }
+local block = { skin = "2tie", colour = "A", oob=false }
+
+function Grid:new(width, height)
+ self.grid = {}
+ self.grid_age = {}
+ self.width = width
+ self.height = height
+ for y = 1, self.height do
+ self.grid[y] = {}
+ self.grid_age[y] = {}
+ for x = 1, self.width do
+ self.grid[y][x] = empty
+ self.grid_age[y][x] = 0
+ end
+ end
+end
+
+function Grid:clear()
+ for y = 1, self.height do
+ for x = 1, self.width do
+ self.grid[y][x] = empty
+ self.grid_age[y][x] = 0
+ end
+ end
+end
+
+function Grid:getCell(x, y)
+ if x < 1 or x > self.width or y > self.height then return oob
+ elseif y < 1 then return oob
+ else return self.grid[y][x]
+ end
+end
+
+function Grid:isOccupied(x, y)
+ return self:getCell(x+1, y+1) ~= empty
+end
+
+function Grid:isRowFull(row)
+ for index, square in pairs(self.grid[row]) do
+ if square == empty then return false end
+ end
+ return true
+end
+
+function Grid:canPlacePiece(piece)
+ local offsets = piece:getBlockOffsets()
+ for index, offset in pairs(offsets) do
+ local x = piece.position.x + offset.x
+ local y = piece.position.y + offset.y
+ if self:isOccupied(x, y) then
+ return false
+ end
+ end
+ return true
+end
+
+function Grid:canPlacePieceInVisibleGrid(piece)
+ local offsets = piece:getBlockOffsets()
+ for index, offset in pairs(offsets) do
+ local x = piece.position.x + offset.x
+ local y = piece.position.y + offset.y
+ if y < 1 or self:isOccupied(x, y) ~= empty then
+ return false
+ end
+ end
+ return true
+end
+
+function Grid:getClearedRowCount()
+ local count = 0
+ local cleared_row_table = {}
+ for row = 1, self.height do
+ if self:isRowFull(row) then
+ count = count + 1
+ table.insert(cleared_row_table, row)
+ end
+ end
+ return count, cleared_row_table
+end
+
+function Grid:markClearedRows()
+ local block_table = {}
+ for row = 1, self.height do
+ if self:isRowFull(row) then
+ block_table[row] = {}
+ for x = 1, self.width do
+ block_table[row][x] = {
+ skin = self.grid[row][x].skin,
+ colour = self.grid[row][x].colour,
+ }
+ self.grid[row][x] = {
+ skin = self.grid[row][x].skin,
+ colour = "X"
+ }
+ end
+ end
+ end
+ return block_table
+end
+
+function Grid:clearClearedRows()
+ for row = 1, self.height do
+ if self:isRowFull(row) then
+ for above_row = row, 2, -1 do
+ self.grid[above_row] = self.grid[above_row - 1]
+ self.grid_age[above_row] = self.grid_age[above_row - 1]
+ end
+ self.grid[1] = {}
+ self.grid_age[1] = {}
+ for i = 1, self.width do
+ self.grid[1][i] = empty
+ self.grid_age[1][i] = 0
+ end
+ end
+ end
+ return true
+end
+
+function Grid:clearSpecificRow(row)
+ for col = 1, self.width do
+ self.grid[row][col] = empty
+ end
+end
+
+function Grid:applyPiece(piece)
+ if piece.big then
+ self:applyBigPiece(piece)
+ return
+ end
+ for index, offset in pairs(piece:getBlockOffsets()) do
+ local x = piece.position.x + offset.x
+ local y = piece.position.y + offset.y
+ if y + 1 > 0 and y < self.height then
+ self.grid[y+1][x+1] = {
+ skin = piece.skin,
+ colour = piece.colour,
+ flash = 5
+ }
+ end
+ end
+end
+
+function Grid:checkStackHeight()
+ for i = 0, self.height - 1 do
+ for j = 0, self.width - 1 do
+ if self:isOccupied(j, i) then return self.height - i end
+ end
+ end
+ return 0
+end
+
+function Grid:applyMap(map)
+ for y, row in pairs(map) do
+ for x, block in pairs(row) do
+ self.grid_age[y][x] = 0
+ self.grid[y][x] = block
+ end
+ end
+end
+
+function Grid:update()
+ for y = 1, self.height do
+ for x = 1, self.width do
+ if self.grid[y][x] ~= empty then
+ self.grid_age[y][x] = self.grid_age[y][x] + 1
+ end
+ end
+ end
+end
+
+function Grid:draw(greyscale, timer)
+ love.graphics.setColor(0,0,0,1)
+ love.graphics.rectangle("fill", 256, 31, 80, 45, 0, 0)
+ love.graphics.setColor(0.3, 0.3, 0.3, 1)
+ love.graphics.line(256,31,256,31+45)
+ love.graphics.line(256,31,256+80,31)
+ love.graphics.line(256+80,31,256+80,31+45)
+ love.graphics.line(256,31+45,256+80,31+45)
+ for y = 1, self.height do
+ for x = 1, self.width do
+ if BLOCKS[self.grid[y][x].skin] and
+ BLOCKS[self.grid[y][x].skin][self.grid[y][x].colour] then
+ if self.grid[y][x].flash ~= nil then
+ if self.grid[y][x].flash > 0 then
+ love.graphics.setColor(0.4+(self.grid[y][x].flash*0.1), 0.4+(self.grid[y][x].flash*0.1), 0.4+(self.grid[y][x].flash*0.1), 1)
+ love.graphics.draw(BLOCKS[self.grid[y][x].skin]['W'], 200+x*16, 64+y*16)
+ self.grid[y][x].flash = self.grid[y][x].flash - 1
+ else
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.draw(BLOCKS[self.grid[y][x].skin][self.grid[y][x].colour..'_d'], 200+x*16, 64+y*16)
+ end
+ end
+ if greyscale then
+ if timer > 1 then timer = 1 end
+ love.graphics.setColor(0.7, 0.7, 0.7, 0+timer)
+ if self.grid[y][x] ~= empty and self.grid[y][x].colour ~= 'X' then
+ love.graphics.draw(BLOCKS[self.grid[y][x].skin]["A"], 200+x*16, 64+y*16)
+ end
+ end
+ end
+ end
+ end
+end
+
+return Grid
diff --git a/game/piece.lua b/game/piece.lua
new file mode 100644
index 0000000..5594ca3
--- /dev/null
+++ b/game/piece.lua
@@ -0,0 +1,174 @@
+local Object = require 'libs.classic'
+
+local Piece = Object:extend()
+
+function Piece:new(shape, rotation, position, block_offsets, gravity, lock_delay, skin, colour, big)
+ self.shape = shape
+ self.rotation = rotation
+ self.position = position
+ self.block_offsets = block_offsets
+ self.gravity = gravity
+ self.lock_delay = lock_delay
+ self.skin = skin
+ self.colour = colour
+ self.lowest_point = -1
+-- self.ghost = false
+ self.locked = false
+-- self.big = big
+end
+
+-- Functions that return a new piece to test in rotation systems.
+
+function Piece:withOffset(offset)
+ return Piece(
+ self.shape, self.rotation,
+ {x = self.position.x + offset.x, y = self.position.y + offset.y},
+ self.block_offsets, self.gravity, self.lock_delay, self.skin, self.colour
+ )
+end
+
+function Piece:withRelativeRotation(rot)
+ local new_rot = self.rotation + rot
+ while new_rot < 0 do new_rot = new_rot + 4 end
+ while new_rot >= 4 do new_rot = new_rot - 4 end
+ return Piece(
+ self.shape, new_rot, self.position,
+ self.block_offsets, self.gravity, self.lock_delay, self.skin, self.colour
+ )
+end
+
+-- Functions that return predicates relative to a grid.
+
+function Piece:getBlockOffsets()
+ return self.block_offsets[self.shape][self.rotation + 1]
+end
+
+function Piece:occupiesSquare(x, y)
+ local offsets = self:getBlockOffsets()
+ for index, offset in pairs(offsets) do
+ local new_offset = {x = self.position.x + offset.x, y = self.position.y + offset.y}
+ if new_offset.x == x and new_offset.y == y then
+ return true
+ end
+ end
+ return false
+end
+
+function Piece:isMoveBlocked(grid, offset)
+ local moved_piece = self:withOffset(offset)
+ return not grid:canPlacePiece(moved_piece)
+end
+
+function Piece:isDropBlocked(grid)
+ return self:isMoveBlocked(grid, { x=0, y=1 })
+end
+
+-- Procedures to actually do stuff to pieces.
+
+function Piece:setOffset(offset)
+ self.position.x = self.position.x + offset.x
+ self.position.y = self.position.y + offset.y
+ return self
+end
+
+function Piece:setRelativeRotation(rot)
+ new_rot = self.rotation + rot
+ while new_rot < 0 do new_rot = new_rot + 4 end
+ while new_rot >= 4 do new_rot = new_rot - 4 end
+ self.rotation = new_rot
+ return self
+end
+
+function Piece:moveInGrid(step, squares, grid, instant)
+ local moved = false
+ for x = 1, squares do
+ if grid:canPlacePiece(self:withOffset(step)) then
+ moved = true
+ self:setOffset(step)
+ if instant then
+ self:dropToBottom(grid)
+ end
+ else
+ break
+ end
+ end
+ return self
+end
+
+function Piece:dropSquares(dropped_squares, grid)
+ self:moveInGrid({ x = 0, y = 1 }, dropped_squares, grid)
+end
+
+function Piece:dropToBottom(grid)
+ local piece_y = self.position.y
+ self:dropSquares(math.huge, grid)
+ self.gravity = 0
+ if self.position.y > piece_y then
+ --self.lock_delay = 0
+ end
+ return self
+end
+
+function Piece:lockIfBottomed(grid)
+ if self:isDropBlocked(grid) then
+ self.locked = true
+ end
+ return self
+end
+
+function Piece:addGravity(gravity, grid, classic_lock)
+ gravity = gravity / (self.big and 2 or 1)
+ local new_gravity = self.gravity + gravity
+ if self:isDropBlocked(grid) then
+ if classic_lock then
+ self.gravity = new_gravity
+ else
+ self.gravity = 0
+ self.lock_delay = self.lock_delay + 1
+ end
+ elseif not (
+ self:isMoveBlocked(grid, { x=0, y=-1 }) and gravity < 0
+ ) then
+ local dropped_squares = math.floor(math.abs(new_gravity))
+ if gravity >= 0 then
+ local new_frac_gravity = new_gravity - dropped_squares
+ self.gravity = new_frac_gravity
+ self:dropSquares(dropped_squares, grid)
+ if self:isDropBlocked(grid) then
+ PlaySE("bottom")
+ end
+ else
+ local new_frac_gravity = new_gravity + dropped_squares
+ self.gravity = new_frac_gravity
+ self:moveInGrid({ x=0, y=-1 }, dropped_squares, grid)
+ if self:isMoveBlocked(grid, { x=0, y=-1 }) then
+ PlaySE("bottom")
+ end
+ end
+ else
+ self.gravity = 0
+ end
+ return self
+end
+
+-- Procedures for drawing.
+
+function Piece:draw(opacity, brightness, grid, partial_das)
+ if opacity == nil then opacity = 1 end
+ if brightness == nil then brightness = 1 end
+ love.graphics.setColor(brightness, brightness, brightness, opacity)
+ local offsets = self:getBlockOffsets()
+ local gravity_offset = 0
+ if partial_das == nil then partial_das = 0 end
+ for index, offset in pairs(offsets) do
+ local x = self.position.x + offset.x
+ local y = self.position.y + offset.y
+ love.graphics.draw(
+ BLOCKS[self.skin][self.colour],
+ 216+x*16+partial_das, 80+y*16+gravity_offset
+ )
+ end
+ return false
+end
+
+return Piece
diff --git a/game/randomizer.lua b/game/randomizer.lua
new file mode 100644
index 0000000..f313a92
--- /dev/null
+++ b/game/randomizer.lua
@@ -0,0 +1,148 @@
+local Object = require 'libs.classic'
+
+local Randomizer = Object:extend()
+
+local highest_count = 0
+
+function Randomizer:new(always, seed)
+ self.always = always
+ if seed ~= nil then
+ self.seed = seed
+ else
+ self.seed = love.math.random(1, 9007199254740991)
+ end
+ self.drought = {
+ I = 0,
+ J = 0,
+ L = 0,
+ T = 0,
+ S = 0,
+ Z = 0,
+ O = 0
+ }
+ self.droughted_deals = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+ self.piece_counts = {
+ I = 0,
+ J = 0,
+ L = 0,
+ T = 0,
+ S = 0,
+ Z = 0,
+ O = 0
+ }
+ self.piece_round = 0
+ self:initialize()
+end
+
+function Randomizer:nextPiece()
+ return self:generatePiece()
+end
+
+function Randomizer:runTest()
+ local i_distance = 0
+ local highest_distance = 0
+ local distance_total = 0
+ local i_appearances = 0
+ local dupe = 0
+ local trip = 0
+ local quad = 0
+ local quin = 0
+ local lastpiece = 0
+ local lastpiece2 = 0
+ local lastpiece3 = 0
+ local lastpiece4 = 0
+ local pieceseq_rep = 0
+ local pieceseq = {}
+ local last_pieceseq = {}
+ local highest_discrepancy = 0
+ local discrepancy = self.piece_counts[table.highest(self.piece_counts)] - self.piece_counts[table.lowest(self.piece_counts)]
+ local total_chance = 0
+ for i=0, 750000 do
+ piece = self:generatePiece()
+ if #pieceseq < 7 then
+ table.insert(pieceseq, piece)
+ else
+ --print(table.concat(pieceseq))
+ if table.concat(pieceseq) == table.concat(last_pieceseq) then
+ pieceseq_rep = pieceseq_rep + 1
+ end
+ last_pieceseq = copy(pieceseq)
+ pieceseq = {}
+ end
+ if piece == lastpiece then dupe = dupe + 1 end
+ if piece == lastpiece and piece == lastpiece2 then trip = trip + 1 end
+ if piece == lastpiece and piece == lastpiece2 and piece == lastpiece3 then quad = quad + 1 end
+ if piece == lastpiece and piece == lastpiece2 and piece == lastpiece3 and piece == lastpiece4 then quin = quin + 1 end
+ if piece == 'I' then
+ distance_total = distance_total + i_distance
+ i_appearances = i_appearances + 1
+ i_distance = 0
+ else i_distance = i_distance + 1 end
+ if highest_distance < i_distance then highest_distance = i_distance end
+ lastpiece4 = lastpiece3
+ lastpiece3 = lastpiece2
+ lastpiece2 = lastpiece
+ lastpiece = piece
+ --if self.piece_round <= 750 then print(discrepancy) end
+ local new_discrepancy = self.piece_counts[table.highest(self.piece_counts)] - self.piece_counts[table.lowest(self.piece_counts)]
+ if new_discrepancy > discrepancy then highest_discrepancy = new_discrepancy end
+ discrepancy = new_discrepancy
+ local chance = 0
+ for piece,drought in pairs(self.drought) do
+ local piece_chance = 0.14/( ((self.drought[table.highest(self.drought)]+1)-drought) / (self.drought[table.highest(self.drought)]) )
+ chance = chance + piece_chance
+ end
+ chance = chance / 7
+ total_chance = total_chance + chance
+ end
+ --something = something / 750000
+ print(string.format('dupes: %d, trips: %s, quads: %s, quins: %s, highest i distance: %d, average i distance: %f, pieceseq reps: %d, highest discrepancy:%d, average chance:%f\ndrought lengths dealt: %s', dupe, trip, quad, quin, highest_distance, distance_total/i_appearances, pieceseq_rep, highest_discrepancy, total_chance/750000, table.concat(self.droughted_deals, '-')))
+ for piece,count in pairs(self.piece_counts) do
+ print(piece..' '..count)
+ end
+end
+
+function Randomizer:initialize()
+ local start_pieces = {"I", "J", "L", "T"}
+ local shapes = {"I", "J", "L", "T", "S", "Z", "O"}
+ love.math.setRandomSeed(self.seed)
+ self.drought[table.remove(shapes, love.math.random(1, 4))] = 10
+ for i=4,9 do
+ self.drought[table.remove(shapes, love.math.random(1, #shapes))] = i
+ end
+
+ --self:runTest()
+end
+
+function Randomizer:generatePiece(always)
+ if self.always then
+ return "I"
+ end
+
+ local shapes = {"I", "J", "L", "T", "S", "Z", "O"}
+ local new_piece = shapes[love.math.random(1,7)]
+ local chance = love.math.random(0,self.drought[table.highest(self.drought)])
+ while self.drought[new_piece] < chance do
+ new_piece = shapes[love.math.random(1,7)]
+ end
+ for piece,drought in pairs(self.drought) do
+ if drought >= 10 then
+ new_piece = piece
+ end
+ end
+ generated_piece = new_piece
+
+ for piece,drought in pairs(self.drought) do
+ if new_piece ~= piece then self.drought[piece] = self.drought[piece] + 1
+ else
+ if drought < #self.droughted_deals then self.droughted_deals[drought+1] = self.droughted_deals[drought+1] + 1 end
+ self.piece_counts[piece] = self.piece_counts[piece] + 1
+ self.drought[piece] = 0
+ end
+ end
+ self.piece_round = self.piece_round + 1
+
+ return generated_piece
+end
+
+return Randomizer
diff --git a/game/rotation.lua b/game/rotation.lua
new file mode 100644
index 0000000..49b04d7
--- /dev/null
+++ b/game/rotation.lua
@@ -0,0 +1,299 @@
+local Object = require 'libs.classic'
+local Piece = require 'game.piece'
+
+local Rotation = Object:extend()
+
+Rotation.spawn_positions = {
+ I = { x=3, y= -1 },
+ J = { x=3, y= -1 },
+ L = { x=3, y= -1 },
+ O = { x=3, y= -1 },
+ S = { x=3, y= -1 },
+ T = { x=3, y= -1 },
+ Z = { x=3, y= -1 },
+}
+
+Rotation.colourscheme = {
+ I = "R",
+ L = "O",
+ J = "B",
+ S = "M",
+ Z = "G",
+ O = "Y",
+ T = "C",
+}
+
+Rotation.block_offsets = {
+ I={
+ { {x=0, y=1}, {x=1, y=1}, {x=2, y=1}, {x=3, y=1} },
+ { {x=2, y=0}, {x=2, y=1}, {x=2, y=2}, {x=2, y=3} },
+ { {x=0, y=1}, {x=1, y=1}, {x=2, y=1}, {x=3, y=1} },
+ { {x=2, y=0}, {x=2, y=1}, {x=2, y=2}, {x=2, y=3} },
+ },
+ J={
+ { {x=0, y=1}, {x=1, y=1}, {x=2, y=1}, {x=2, y=2} },
+ { {x=1, y=0}, {x=1, y=1}, {x=1, y=2}, {x=0, y=2} },
+ { {x=0, y=1}, {x=0, y=2}, {x=1, y=2}, {x=2, y=2} },
+ { {x=1, y=0}, {x=2, y=0}, {x=1, y=1}, {x=1, y=2} },
+ },
+ L={
+ { {x=0, y=1}, {x=0, y=2}, {x=1, y=1}, {x=2, y=1} },
+ { {x=0, y=0}, {x=1, y=0}, {x=1, y=1}, {x=1, y=2} },
+ { {x=0, y=2}, {x=1, y=2}, {x=2, y=1}, {x=2, y=2} },
+ { {x=1, y=0}, {x=1, y=1}, {x=1, y=2}, {x=2, y=2} },
+ },
+ O={
+ { {x=1, y=1}, {x=2, y=1}, {x=1, y=2}, {x=2, y=2} },
+ { {x=1, y=1}, {x=2, y=1}, {x=1, y=2}, {x=2, y=2} },
+ { {x=1, y=1}, {x=2, y=1}, {x=1, y=2}, {x=2, y=2} },
+ { {x=1, y=1}, {x=2, y=1}, {x=1, y=2}, {x=2, y=2} },
+ },
+ S={
+ { {x=1, y=1}, {x=2, y=1}, {x=0, y=2}, {x=1, y=2} },
+ { {x=0, y=0}, {x=0, y=1}, {x=1, y=1}, {x=1, y=2} },
+ { {x=1, y=1}, {x=2, y=1}, {x=0, y=2}, {x=1, y=2} },
+ { {x=0, y=0}, {x=0, y=1}, {x=1, y=1}, {x=1, y=2} },
+ },
+ T={
+ { {x=0, y=1}, {x=1, y=1}, {x=1, y=2}, {x=2, y=1} },
+ { {x=0, y=1}, {x=1, y=0}, {x=1, y=1}, {x=1, y=2} },
+ { {x=0, y=2}, {x=1, y=1}, {x=1, y=2}, {x=2, y=2} },
+ { {x=1, y=0}, {x=1, y=1}, {x=2, y=1}, {x=1, y=2} },
+ },
+ Z={
+ { {x=0, y=1}, {x=1, y=1}, {x=1, y=2}, {x=2, y=2} },
+ { {x=2, y=0}, {x=1, y=1}, {x=2, y=1}, {x=1, y=2} },
+ { {x=0, y=1}, {x=1, y=1}, {x=1, y=2}, {x=2, y=2} },
+ { {x=2, y=0}, {x=1, y=1}, {x=2, y=1}, {x=1, y=2} },
+ }
+}
+
+Rotation.pieces = 7
+
+-- Component functions.
+
+function Rotation:new(game_mode)
+ self.game = require 'game.gamemode'
+end
+
+function Rotation:rotatePiece(inputs, piece, grid, prev_inputs, initial, lastdir)
+ local new_inputs = {}
+
+ for input, value in pairs(inputs) do
+ if value and not prev_inputs[input] then
+ new_inputs[input] = true
+ end
+ end
+ local was_drop_blocked = piece:isDropBlocked(grid)
+
+ if self:canPieceRotate(piece, grid) then
+-- if not self.held_rotate then
+-- self:attemptRotate(inputs, piece, grid, initial)
+-- self.held_rotate = true
+-- else
+ self:attemptRotate(new_inputs, piece, grid, initial, lastdir)
+-- end
+ end
+
+ if not initial and not was_drop_blocked and piece:isDropBlocked(grid) then
+ PlaySE("bottom")
+ end
+
+ -- prev_inputs becomes the previous inputs
+ for input, value in pairs(inputs) do
+ prev_inputs[input] = inputs[input]
+ end
+end
+
+function Rotation:attemptRotate(new_inputs, piece, grid, initial, lastdir)
+ local rot_dir = 0
+
+ if (new_inputs["rotate_left"] or new_inputs["rotate_left2"]) then
+ rot_dir = 3
+ if lastdir == 0 then lastdir = -1 end
+ elseif (new_inputs["rotate_right"] or new_inputs["rotate_right2"]) then
+ rot_dir = 1
+ if lastdir == 0 then lastdir = 1 end
+ end
+ if rot_dir == 0 then return end
+
+ local new_piece = piece:withRelativeRotation(rot_dir)
+ self:attemptWallkicks(piece, new_piece, rot_dir, grid, lastdir)
+end
+
+function Rotation:attemptWallkicks(piece, new_piece, rot_dir, grid, lastdir)
+ -- wallkick routine designed to maximize flexibility while minimizing teleports
+
+ -- O doesn't kick
+ if (piece.shape == "O") then return end
+
+ -- assess precisely what rows/columns would be blocked given the desired rotation
+ local sides = {top=false,uright=false,lright=false,uleft=false,lleft=false,center=false,bottom=false}
+ local left_exists = false
+ local right_exists = false
+ local kick = {x=0,y=0}
+ for _,offset in pairs(new_piece:getBlockOffsets()) do
+ local x = piece.position.x + offset.x
+ local y = piece.position.y + offset.y
+ if offset.x == 0 then left_exists = true end
+ if offset.x == 2 or offset.x == 3 then right_exists = true end
+
+ if grid:isOccupied(x,y) then
+ if offset.y == 0 then sides.top = true end
+ if offset.y == 3 then sides.bottom = true end
+ if offset.y == 1 and offset.x == 0 then sides.uleft = true end
+ if offset.y == 2 and offset.x == 0 then sides.lleft = true end
+ if offset.y == 1 and (offset.x == 2 or offset.x == 3) then sides.uright = true end
+ if offset.y == 2 and (offset.x == 2 or offset.x == 3) then sides.lright = true end
+ if offset.x == 1 then sides.center = true end
+ end
+ end
+
+ if sides.top then kick = {x=0,y=1}
+ elseif (sides.uleft and sides.lright) or (sides.uright and sides.lleft) or (sides.uright and sides.uleft) then kick = {x=0,y=1}
+ elseif (sides.lleft and sides.lright) then kick = {x=0,y=-1}
+ elseif (sides.lleft or sides.uleft) then kick = {x=1,y=0}
+ elseif (sides.lright or sides.uright) then kick = {x=-1,y=0}
+ elseif sides.center and left_exists then kick = {x=1,y=0}
+ elseif sides.center and right_exists then kick = {x=-1,y=0}
+ elseif sides.bottom then kick = {x=0,y=-1}
+ end
+
+
+ if grid:canPlacePiece(new_piece:withOffset({x=0,y=0})) then
+ self:onPieceRotate(piece, grid)
+ piece:setRelativeRotation(rot_dir):setOffset({x=0,y=0})
+ elseif grid:canPlacePiece(new_piece:withOffset(kick)) then
+ self:onPieceRotate(piece, grid)
+ piece:setRelativeRotation(rot_dir):setOffset(kick)
+ end
+end
+
+function Rotation:movePiece(piece, grid, move, instant)
+ local was_drop_blocked = piece:isDropBlocked(grid)
+ local offset = ({x=0, y=0})
+ local moves = 0
+ local y = piece.position.y
+ if move == "left" then
+ offset.x = -1
+ moves = 1
+ elseif move == "right" then
+ offset.x = 1
+ moves = 1
+ elseif move == "speedleft" then
+ offset.x = -1
+ moves = grid.width
+ elseif move == "speedright" then
+ offset.x = 1
+ moves = grid.width
+ end
+ if not self:canPieceMove(piece, grid) then return end
+ for i = 1, moves do
+ local x = piece.position.x
+ if moves ~= 1 then
+ piece:moveInGrid(offset, 1, grid, instant)
+ else
+ piece:moveInGrid(offset, 1, grid, false)
+ end
+ if piece.position.x ~= x then
+ self:onPieceMove(piece, grid)
+ if piece.locked then break end
+ end
+ end
+ if not was_drop_blocked and piece:isDropBlocked(grid) then
+ PlaySE("bottom")
+ end
+end
+
+function Rotation:dropPiece(
+ inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
+ hard_drop_enabled, additive_gravity, classic_lock
+)
+ local y = piece.position.y
+ if inputs["down"] == true and drop_locked == false then
+ piece:addGravity(math.max(gravity, drop_speed), grid, classic_lock)
+ else
+ piece:addGravity(gravity, grid, classic_lock)
+ end
+ if piece.position.y ~= y then
+ self:onPieceDrop(piece, grid)
+ end
+end
+
+function Rotation:lockPiece(piece, grid, lock_delay, classic_lock)
+ if piece:isDropBlocked(grid) and piece.lock_delay >= lock_delay then
+ piece.locked = true
+ end
+end
+
+function Rotation:get180RotationValue() return 3 end
+function Rotation:getDefaultOrientation() return 1 end
+function Rotation:getDrawOffset(shape, orientation) return { x=0, y=0 } end
+function Rotation:getAboveFieldOffset(shape, orientation)
+ if shape == "I" then
+ return 1
+ else
+ return 2
+ end
+end
+
+function Rotation:initializePiece(
+ inputs, data, grid, gravity, prev_inputs,
+ move, lock_delay, drop_speed,
+ drop_locked, hard_drop_locked, big, irs, lastdir
+)
+ local spawn_positions
+ spawn_positions = self.spawn_positions
+
+ local colours
+ colours = self.colourscheme
+ self.last_dir = 0
+ self.held_rotate = false
+ if inputs['left'] then self.last_dir = -1
+ elseif inputs['right'] then self.last_dir = 1 end
+ local spawn_x = math.floor(spawn_positions[data.shape].x / 10 * grid.width)
+
+ local spawn_dy
+ spawn_dy = 0
+
+ local piece = Piece(data.shape, data.orientation - 1, {
+ x = spawn_x or spawn_positions[data.shape].x,
+ y = spawn_positions[data.shape].y - spawn_dy
+ }, self.block_offsets, 0, 0, data.skin, colours[data.shape], big)
+
+ self:onPieceCreate(piece)
+ if irs then
+ self:rotatePiece(inputs, piece, grid, {}, true, lastdir)
+ end
+ return piece
+end
+
+function Rotation:onPieceCreate(piece) end
+
+function Rotation:processPiece(
+ inputs, piece, grid, gravity, prev_inputs,
+ move, lock_delay, drop_speed,
+ drop_locked, hard_drop_locked,
+ hard_drop_enabled, additive_gravity, classic_lock, lastdir
+)
+ self:rotatePiece(inputs, piece, grid, prev_inputs, false, lastdir)
+ self:movePiece(piece, grid, move, gravity >= grid.height)
+ self:dropPiece(
+ inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
+ hard_drop_enabled, additive_gravity, classic_lock
+ )
+ self:lockPiece(piece, grid, lock_delay, classic_lock)
+end
+
+function Rotation:canPieceMove(piece, grid) return true end
+function Rotation:canPieceRotate(piece, grid) return true end
+function Rotation:onPieceMove(piece) end
+function Rotation:onPieceRotate(piece) end
+function Rotation:onPieceDrop(piece)
+ if piece.position.y > piece.lowest_point then
+ piece.lock_delay = 0
+ piece.lowest_point = piece.position.y
+ end
+end
+
+return Rotation
diff --git a/game/rotation_pent.lua b/game/rotation_pent.lua
new file mode 100644
index 0000000..d1bd009
--- /dev/null
+++ b/game/rotation_pent.lua
@@ -0,0 +1,287 @@
+local Object = require 'libs.classic'
+local Piece = require 'game.piece'
+
+local Rotation = Object:extend()
+
+Rotation.spawn_positions = {
+ I = { x=3, y= -2 },
+ J = { x=3, y= -2 },
+ L = { x=3, y= -2 },
+ O = { x=3, y= -2 },
+ S = { x=3, y= -2 },
+ T = { x=3, y= -2 },
+ Z = { x=3, y= -2 },
+}
+
+Rotation.colourscheme = {
+ I = "R",
+ L = "O",
+ J = "B",
+ S = "M",
+ Z = "G",
+ O = "Y",
+ T = "C",
+}
+
+Rotation.block_offsets = {
+ I={
+ { {x=0, y=2}, {x=1, y=2}, {x=2, y=2}, {x=3, y=2}, {x=4, y=2} },
+ { {x=2, y=0}, {x=2, y=1}, {x=2, y=2}, {x=2, y=3}, {x=2, y=4} },
+ { {x=0, y=2}, {x=1, y=2}, {x=2, y=2}, {x=3, y=2}, {x=4, y=2} },
+ { {x=2, y=0}, {x=2, y=1}, {x=2, y=2}, {x=2, y=3}, {x=2, y=4} },
+ },
+ J={
+ { {x=0, y=2}, {x=1, y=2}, {x=2, y=2}, {x=2, y=3}, {x=3, y=2} },
+ { {x=1, y=0}, {x=1, y=1}, {x=1, y=2}, {x=0, y=2}, {x=1, y=3} },
+ { {x=1, y=2}, {x=1, y=3}, {x=2, y=3}, {x=3, y=3}, {x=0, y=3} },
+ { {x=1, y=1}, {x=2, y=1}, {x=1, y=2}, {x=1, y=3}, {x=1, y=0} },
+ },
+ L={
+ { {x=0, y=2}, {x=0, y=3}, {x=1, y=2}, {x=2, y=2}, {x=3, y=2} },
+ { {x=0, y=0}, {x=1, y=0}, {x=1, y=1}, {x=1, y=2}, {x=1, y=3} },
+ { {x=1, y=3}, {x=2, y=3}, {x=3, y=2}, {x=3, y=3}, {x=0, y=3} },
+ { {x=1, y=1}, {x=1, y=2}, {x=1, y=3}, {x=2, y=3}, {x=1, y=0} },
+ },
+ O={
+ { {x=0, y=2}, {x=0, y=3}, {x=1, y=2}, {x=2, y=2}, {x=1, y=3} },
+ { {x=0, y=1}, {x=1, y=1}, {x=1, y=2}, {x=1, y=3}, {x=0, y=2} },
+ { {x=0, y=3}, {x=1, y=3}, {x=2, y=2}, {x=2, y=3}, {x=1, y=2} },
+ { {x=1, y=1}, {x=1, y=2}, {x=1, y=3}, {x=2, y=3}, {x=2, y=2} },
+ },
+ S={
+ { {x=1, y=2}, {x=2, y=2}, {x=0, y=3}, {x=1, y=3}, {x=3, y=2} },
+ { {x=0, y=0}, {x=0, y=1}, {x=1, y=1}, {x=1, y=2}, {x=1, y=3} },
+ { {x=2, y=2}, {x=3, y=2}, {x=1, y=3}, {x=2, y=3}, {x=0, y=3} },
+ { {x=1, y=1}, {x=1, y=2}, {x=2, y=2}, {x=2, y=3}, {x=1, y=0} },
+ },
+ T={
+ { {x=0, y=2}, {x=1, y=2}, {x=1, y=3}, {x=2, y=2}, {x=3, y=2} },
+ { {x=0, y=1}, {x=1, y=0}, {x=1, y=1}, {x=1, y=2}, {x=1, y=3} },
+ { {x=1, y=3}, {x=2, y=2}, {x=2, y=3}, {x=3, y=3}, {x=0, y=3} },
+ { {x=2, y=1}, {x=2, y=2}, {x=3, y=2}, {x=2, y=3}, {x=2, y=0} },
+ },
+ Z={
+ { {x=0, y=2}, {x=1, y=2}, {x=1, y=3}, {x=2, y=3}, {x=2, y=2} },
+ { {x=1, y=1}, {x=0, y=2}, {x=1, y=2}, {x=0, y=3}, {x=1, y=3} },
+ { {x=0, y=2}, {x=1, y=2}, {x=1, y=3}, {x=2, y=3}, {x=0, y=3} },
+ { {x=2, y=1}, {x=1, y=2}, {x=2, y=2}, {x=1, y=3}, {x=1, y=1} },
+ }
+}
+
+Rotation.pieces = 7
+
+-- Component functions.
+
+function Rotation:new(game_mode)
+ self.game = require 'game.gamemode'
+end
+
+function Rotation:rotatePiece(inputs, piece, grid, prev_inputs, initial, lastdir)
+ local new_inputs = {}
+
+ for input, value in pairs(inputs) do
+ if value and not prev_inputs[input] then
+ new_inputs[input] = true
+ end
+ end
+ local was_drop_blocked = piece:isDropBlocked(grid)
+
+ if self:canPieceRotate(piece, grid) then
+-- if not self.held_rotate then
+-- self:attemptRotate(inputs, piece, grid, initial)
+-- self.held_rotate = true
+-- else
+ self:attemptRotate(new_inputs, piece, grid, initial, lastdir)
+-- end
+ end
+
+ if not initial and not was_drop_blocked and piece:isDropBlocked(grid) then
+ PlaySE("bottom")
+ end
+
+ -- prev_inputs becomes the previous inputs
+ for input, value in pairs(inputs) do
+ prev_inputs[input] = inputs[input]
+ end
+end
+
+function Rotation:attemptRotate(new_inputs, piece, grid, initial, lastdir)
+ local rot_dir = 0
+
+ if (new_inputs["rotate_left"] or new_inputs["rotate_left2"]) then
+ rot_dir = 3
+ if lastdir == 0 then lastdir = -1 end
+ elseif (new_inputs["rotate_right"] or new_inputs["rotate_right2"]) then
+ rot_dir = 1
+ if lastdir == 0 then lastdir = 1 end
+ end
+ if rot_dir == 0 then return end
+
+ local new_piece = piece:withRelativeRotation(rot_dir)
+ self:attemptWallkicks(piece, new_piece, rot_dir, grid, lastdir)
+end
+
+function Rotation:attemptWallkicks(piece, new_piece, rot_dir, grid, lastdir)
+ -- assess precisely what rows/columns would be blocked given the desired rotation
+ local sides = {top=false,uright=false,lright=false,uleft=false,lleft=false,center=false,bottom=false}
+ local left_exists = false
+ local right_exists = false
+ local kick = {x=0,y=0}
+ for _,offset in pairs(new_piece:getBlockOffsets()) do
+ local x = piece.position.x + offset.x
+ local y = piece.position.y + offset.y
+
+ if grid:isOccupied(x,y) then
+ if offset.y <= 1 then sides.top = true end
+ if offset.y == 4 then sides.bottom = true end
+ if offset.x <= 1 then sides.lleft = true end
+ if offset.x >= 2 then sides.lright = true end
+ end
+ end
+
+ if sides.top then kick = {x=0,y=1}
+ elseif sides.bottom then kick = {x=0,y=-1}
+ elseif (sides.lleft or sides.uleft) then kick = {x=1,y=0}
+ elseif (sides.lright or sides.uright) then kick = {x=-1,y=0}
+ end
+
+ if grid:canPlacePiece(new_piece:withOffset({x=0,y=0})) then
+ self:onPieceRotate(piece, grid)
+ piece:setRelativeRotation(rot_dir):setOffset({x=0,y=0})
+ elseif grid:canPlacePiece(new_piece:withOffset(kick)) then
+ self:onPieceRotate(piece, grid)
+ piece:setRelativeRotation(rot_dir):setOffset(kick)
+ elseif grid:canPlacePiece(new_piece:withOffset({x=kick.x*2,y=kick.y*2})) then
+ self:onPieceRotate(piece, grid)
+ piece:setRelativeRotation(rot_dir):setOffset({x=kick.x*2,y=kick.y*2})
+ end
+end
+
+function Rotation:movePiece(piece, grid, move, instant)
+ local was_drop_blocked = piece:isDropBlocked(grid)
+ local offset = ({x=0, y=0})
+ local moves = 0
+ local y = piece.position.y
+ if move == "left" then
+ offset.x = -1
+ moves = 1
+ elseif move == "right" then
+ offset.x = 1
+ moves = 1
+ elseif move == "speedleft" then
+ offset.x = -1
+ moves = grid.width
+ elseif move == "speedright" then
+ offset.x = 1
+ moves = grid.width
+ end
+ if not self:canPieceMove(piece, grid) then return end
+ for i = 1, moves do
+ local x = piece.position.x
+ if moves ~= 1 then
+ piece:moveInGrid(offset, 1, grid, instant)
+ else
+ piece:moveInGrid(offset, 1, grid, false)
+ end
+ if piece.position.x ~= x then
+ self:onPieceMove(piece, grid)
+ if piece.locked then break end
+ end
+ end
+ if not was_drop_blocked and piece:isDropBlocked(grid) then
+ PlaySE("bottom")
+ end
+end
+
+function Rotation:dropPiece(
+ inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
+ hard_drop_enabled, additive_gravity, classic_lock
+)
+ local y = piece.position.y
+ if inputs["down"] == true and drop_locked == false then
+ piece:addGravity(math.max(gravity, drop_speed), grid, classic_lock)
+ else
+ piece:addGravity(gravity, grid, classic_lock)
+ end
+ if piece.position.y ~= y then
+ self:onPieceDrop(piece, grid)
+ end
+end
+
+function Rotation:lockPiece(piece, grid, lock_delay, classic_lock)
+ if piece:isDropBlocked(grid) and piece.lock_delay >= lock_delay then
+ piece.locked = true
+ end
+end
+
+function Rotation:get180RotationValue() return 3 end
+function Rotation:getDefaultOrientation() return 1 end
+function Rotation:getDrawOffset(shape, orientation) return { x=0, y=0 } end
+function Rotation:getAboveFieldOffset(shape, orientation)
+ if shape == "I" then
+ return 1
+ else
+ return 2
+ end
+end
+
+function Rotation:initializePiece(
+ inputs, data, grid, gravity, prev_inputs,
+ move, lock_delay, drop_speed,
+ drop_locked, hard_drop_locked, big, irs, lastdir
+)
+ local spawn_positions
+ spawn_positions = self.spawn_positions
+
+ local colours
+ colours = self.colourscheme
+ self.last_dir = 0
+ self.held_rotate = false
+ if inputs['left'] then self.last_dir = -1
+ elseif inputs['right'] then self.last_dir = 1 end
+ local spawn_x = math.floor(spawn_positions[data.shape].x / 10 * grid.width)
+
+ local spawn_dy
+ spawn_dy = 0
+
+ local piece = Piece(data.shape, data.orientation - 1, {
+ x = spawn_x or spawn_positions[data.shape].x,
+ y = spawn_positions[data.shape].y - spawn_dy
+ }, self.block_offsets, 0, 0, data.skin, colours[data.shape], big)
+
+ self:onPieceCreate(piece)
+ if irs then
+ self:rotatePiece(inputs, piece, grid, {}, true, lastdir)
+ end
+ return piece
+end
+
+function Rotation:onPieceCreate(piece) end
+
+function Rotation:processPiece(
+ inputs, piece, grid, gravity, prev_inputs,
+ move, lock_delay, drop_speed,
+ drop_locked, hard_drop_locked,
+ hard_drop_enabled, additive_gravity, classic_lock, lastdir
+)
+ self:rotatePiece(inputs, piece, grid, prev_inputs, false, lastdir)
+ self:movePiece(piece, grid, move, gravity >= grid.height)
+ self:dropPiece(
+ inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
+ hard_drop_enabled, additive_gravity, classic_lock
+ )
+ self:lockPiece(piece, grid, lock_delay, classic_lock)
+end
+
+function Rotation:canPieceMove(piece, grid) return true end
+function Rotation:canPieceRotate(piece, grid) return true end
+function Rotation:onPieceMove(piece) end
+function Rotation:onPieceRotate(piece) end
+function Rotation:onPieceDrop(piece)
+ if piece.position.y > piece.lowest_point then
+ piece.lock_delay = 0
+ piece.lowest_point = piece.position.y
+ end
+end
+
+return Rotation
diff --git a/game/vctrl.lua b/game/vctrl.lua
new file mode 100644
index 0000000..32d846b
--- /dev/null
+++ b/game/vctrl.lua
@@ -0,0 +1,238 @@
+local gc_newQuad=love.graphics.newQuad
+
+---Get distance between two points
+---@param x1 number
+---@param y1 number
+---@param x2 number
+---@param y2 number
+---@return number
+local function math_distance(x1,y1,x2,y2)
+ return ((x1-x2)^2+(y1-y2)^2)^.5
+end
+
+local function mDrawQ(obj,quad,x,y,a,k)
+ local _,_,w,h=quad:getViewport()
+ love.graphics.draw(obj,quad,x,y,a,k,nil,w*.5,h*.5)
+end
+
+ShowLoadingText('virtual key skin')
+local empty_quad
+-- A table containing quads used to draw icons for virtual control system.
+-- local virtual_quad=setmetatable((function()
+-- local t={}
+-- local w=180
+-- empty_quad=gc_newQuad(0,0,1,1,5*w,7*w)
+-- for i,name in next,{
+-- 'left','right','up','down','',
+-- 'rotate_right','rotate_left','','','',
+-- '','','','','',
+-- '','','','','',
+-- '','','menu_back','','',
+-- '','','','','',
+-- '','','','','menu_decide',
+-- } do if #name>0 then t[name]=gc_newQuad((i-1)%5*w,math.floor((i-1)/5)*w,w,w,5*w,7*w) end end
+-- t.rotate_right2, t.rotate_left2 = t.rotate_right, t.rotate_left
+-- return t
+-- end)(),{
+-- __index=function() return empty_quad end
+-- })
+local virtual_quad=setmetatable((function()
+ local t={}
+ local w=180
+ empty_quad=gc_newQuad(0,0,1,1,5*w,2*w)
+ for i,name in next,{
+ 'left','right','up','down','restart',
+ 'rotate_right','rotate_left','rotate_right2','rotate_left2','',
+ } do if #name>0 then t[name]=gc_newQuad((i-1)%5*w,math.floor((i-1)/5)*w,w,w,5*w,2*w) end end
+ return t
+end)(),{
+ __index=function() return empty_quad end
+})
+local virtual_texture=love.graphics.newImage('game/vctrlTexture.png')
+
+
+local control_type={}
+
+control_type.button={}
+control_type.button.__index=control_type.button
+function control_type.button:new(data)
+ local data=data or {}
+ return setmetatable({
+ show=data.show==nil and true or data.show,
+ x=data.x or 320,
+ y=data.y or 240,
+ r=data.r or 80, -- size
+ shape=data.shape or 'circle',
+ key=data.key or 'X',
+ iconSize=data.iconSize or 80,
+ alpha=data.alpha or 0.75,
+ quad=virtual_quad[data.key]
+ },self)
+end
+function control_type.button:export()
+ return {
+ type = 'button',
+ show = self.show,
+ x = self.x,
+ y = self.y,
+ r = self.r,
+ shape = self.shape,
+ key = self.key,
+ iconSize = self.iconSize,
+ alpha = self.alpha
+ }
+end
+function control_type.button:reset()
+ self.pressed=false
+ self.lastPressTime=-1e99
+ self.pressingID=false
+end
+function control_type.button:press(_,_,id)
+ self.pressed=true
+ self.lastPressTime=love.timer.getTime()
+ self.pressing=id
+ -- love.keypressed(self.key, love.keyboard.getScancodeFromKey(self.key))
+ SCENE:onInputPress{input=self.key,type="virtual"}
+end
+function control_type.button:release()
+ self.pressed=false
+ self.pressingID=false
+ -- love.keyreleased(self.key,love.keyboard.getScancodeFromKey(self.key))
+ SCENE:onInputRelease{input=self.key,type="virtual"}
+end
+function control_type.button:drag(dx,dy)
+ self.x,self.y=self.x+dx,self.y+dy
+end
+function control_type.button:draw(forceAlpha)
+ local alpha = forceAlpha or self.alpha
+ love.graphics.setLineWidth(4)
+ if self.shape=='circle' then
+ love.graphics.setColor(0,0,0,alpha)
+ love.graphics.circle('fill',self.x,self.y,self.r-4)
+
+ love.graphics.setColor(1,1,1,self.pressed and .5 or 0)
+ love.graphics.circle('fill',self.x,self.y,self.r-4)
+
+ love.graphics.setColor(1,1,1,alpha)
+ love.graphics.circle('line',self.x,self.y,self.r-2)
+ elseif self.shape=='square' then
+ love.graphics.setColor(0,0,0,alpha)
+ love.graphics.rectangle('fill',self.x-self.r-4,self.y-self.r-4,self.r*2+8,self.r*2+8)
+
+ love.graphics.setColor(1,1,1,self.pressed and .5 or 0)
+ love.graphics.rectangle('fill',self.x-self.r-4,self.y-self.r-4,self.r*2+8,self.r*2+8)
+
+ love.graphics.setColor(1,1,1,alpha)
+ love.graphics.rectangle('line',self.x-self.r-2,self.y-self.r-2,self.r*2+4,self.r*2+4)
+ end
+ if self.iconSize>0 and self.quad then
+ love.graphics.setColor(1,1,1,alpha)
+ local _,_,w,h=self.quad:getViewport()
+ mDrawQ(
+ virtual_texture,
+ self.quad,
+ self.x,self.y,0,
+ self.iconSize/100*math.min(self.r*2/w,self.r*2/h)
+ )
+ end
+end
+function control_type.button:getDistance(x,y)
+ if self.shape=='circle' then
+ return math_distance(x,y,self.x,self.y)/self.r
+ elseif self.shape=='square' then
+ return math.max(math.abs(x-self.x),math.abs(y-self.y))/self.r
+ end
+end
+
+local touches={}
+local global_toggle=false
+VCTRL={}
+VCTRL.focus=nil -- Focusing buttons
+
+---@class VCTRL.data
+---@field type 'button'
+---@field x number
+---@field y number
+---@field shape? string
+---@field key? string
+---@field iconSize? number
+---@field alpha? number
+---@field show? boolean
+
+---@param ... VCTRL.data[]
+---Adding (multiple) virtual button(s)
+function VCTRL.new(...)
+ for _,d in pairs(...) do table.insert(VCTRL,control_type[d.type]:new(d)) end
+end
+
+---@param toggle boolean|false
+---Enabling virtual control or not
+function VCTRL.toggle(toggle)
+ if not toggle then
+ -- Release all buttons to prevent button ghost situation
+ for id, b in pairs(touches) do
+ b:release(id)
+ touches[id]=nil
+ end
+ end
+ global_toggle=toggle
+end
+
+function VCTRL.clearAll()
+ local toggle = global_toggle
+ VCTRL.toggle(false)
+ global_toggle = toggle
+
+ for i=#VCTRL,1,-1 do VCTRL[i] = nil end
+ collectgarbage()
+end
+
+---@param force? boolean Forcing click on hidden widgets?
+function VCTRL.press(x,y,id,force)
+ if not (global_toggle and id) then return end
+ local obj,closestDist=false,1e99
+ for _, w in ipairs(VCTRL) do
+ if w.show or force then
+ local d=w:getDistance(x,y)
+ if d<=1 and d 0, "bigint is empty")
+ assert(big.sign == "+" or big.sign == "-", "bigint is unsigned")
+ for _, digit in pairs(big.digits) do
+ assert(type(digit) == "number", "at least one digit is invalid")
+ assert(digit <= 9 and digit >= 0, digit .. " is not between 0 and 9")
+ assert(math.floor(digit) == digit, digit .. " is not an integer")
+ end
+ end
+ return true
+end
+
+-- Return a new big with the same digits but with a positive sign (absolute
+-- value)
+function bigint.abs(big)
+ bigint.check(big)
+ local result = big:clone()
+ result.sign = "+"
+ return result
+end
+
+-- Return a new big with the same digits but the opposite sign (negation)
+function bigint.negate(big)
+ bigint.check(big)
+ local result = big:clone()
+ if (result.sign == "+") then
+ result.sign = "-"
+ else
+ result.sign = "+"
+ end
+ return result
+end
+
+-- Return the number of digits in the big
+function bigint.digits(big)
+ bigint.check(big)
+ return #big.digits
+end
+
+-- Convert a big to a number or string
+function bigint.unserialize(big, output_type, precision)
+ bigint.check(big)
+
+ local num = ""
+ if big.sign == "-" then
+ num = "-"
+ end
+
+
+ if ((output_type == nil)
+ or (output_type == "number")
+ or (output_type == "n")
+ or (output_type == "string")
+ or (output_type == "s")) then
+ -- Unserialization to a string or number requires reconstructing the
+ -- entire number
+
+ for _, digit in pairs(big.digits) do
+ num = num .. math.floor(digit) -- lazy way of getting rid of .0$
+ end
+
+ if ((output_type == nil)
+ or (output_type == "number")
+ or (output_type == "n")) then
+ return tonumber(num)
+ else
+ return num
+ end
+
+ else
+ -- Unserialization to human-readable form or scientific notation only
+ -- requires reading the first few digits
+ if (precision == nil) then
+ precision = math.min(#big.digits, 3)
+ else
+ assert(precision > 0, "Precision cannot be less than 1")
+ assert(math.floor(precision) == precision,
+ "Precision must be a positive integer")
+ end
+
+ -- num is the first (precision + 1) digits, the first being separated by
+ -- a decimal point from the others
+ num = num .. math.floor(big.digits[1])
+ if (precision > 1) then
+ num = num .. "."
+ for i = 1, (precision - 1) do
+ num = num .. math.floor(big.digits[i + 1])
+ end
+ end
+
+ if ((output_type == "human-readable")
+ or (output_type == "human")
+ or (output_type == "h"))
+ and (#big.digits >= 3 and #big.digits <= 10002) then
+ -- Human-readable output contributed by 123eee555
+
+ local name
+ local walkback = 0 -- Used to enumerate "ten", "hundred", etc
+
+ -- Walk backwards in the index of named_powers starting at the
+ -- number of digits of the input until the first value is found
+ for i = (#big.digits - 1), (#big.digits - 4), -1 do
+ name = named_powers[i]
+ if (name) then
+ if (walkback == 1) then
+ name = "ten " .. name
+ elseif (walkback == 2) then
+ name = "hundred " .. name
+ end
+ break
+ else
+ walkback = walkback + 1
+ end
+ end
+
+ return num .. " " .. name
+
+ else
+ return num .. "*10^" .. (#big.digits - 1)
+ end
+
+ end
+end
+
+-- Basic comparisons
+-- Accepts symbols (<, >=, ~=) and Unix shell-like options (lt, ge, ne)
+function bigint.compare(big1, big2, comparison)
+ bigint.check(big1)
+ bigint.check(big2)
+
+ local greater = false -- If big1.digits > big2.digits
+ local equal = false
+
+ if (big1.sign == "-") and (big2.sign == "+") then
+ greater = false
+ elseif (#big1.digits > #big2.digits)
+ or ((big1.sign == "+") and (big2.sign == "-")) then
+ greater = true
+ elseif (#big1.digits == #big2.digits) then
+ -- Walk left to right, comparing digits
+ for digit = 1, #big1.digits do
+ if (big1.digits[digit] > big2.digits[digit]) then
+ greater = true
+ break
+ elseif (big2.digits[digit] > big1.digits[digit]) then
+ break
+ elseif (digit == #big1.digits)
+ and (big1.digits[digit] == big2.digits[digit]) then
+ equal = true
+ end
+ end
+
+ end
+
+ -- If both numbers are negative, then the requirements for greater are
+ -- reversed
+ if (not equal) and (big1.sign == "-") and (big2.sign == "-") then
+ greater = not greater
+ end
+
+ return (((comparison == "<") or (comparison == "lt"))
+ and ((not greater) and (not equal)) and true)
+ or (((comparison == ">") or (comparison == "gt"))
+ and ((greater) and (not equal)) and true)
+ or (((comparison == "==") or (comparison == "eq"))
+ and (equal) and true)
+ or (((comparison == ">=") or (comparison == "ge"))
+ and (equal or greater) and true)
+ or (((comparison == "<=") or (comparison == "le"))
+ and (equal or not greater) and true)
+ or (((comparison == "~=") or (comparison == "!=") or (comparison == "ne"))
+ and (not equal) and true)
+ or false
+end
+
+-- BACKEND: Add big1 and big2, ignoring signs
+function bigint.add_raw(big1, big2)
+ bigint.check(big1)
+ bigint.check(big2)
+
+ local result = bigint.new()
+ local max_digits = 0
+ local carry = 0
+
+ if (#big1.digits >= #big2.digits) then
+ max_digits = #big1.digits
+ else
+ max_digits = #big2.digits
+ end
+
+ -- Walk backwards right to left, like in long addition
+ for digit = 0, max_digits - 1 do
+ local sum = (big1.digits[#big1.digits - digit] or 0)
+ + (big2.digits[#big2.digits - digit] or 0)
+ + carry
+
+ if (sum >= 10) then
+ carry = 1
+ sum = sum - 10
+ else
+ carry = 0
+ end
+
+ result.digits[max_digits - digit] = sum
+ end
+
+ -- Leftover carry in cases when #big1.digits == #big2.digits and sum > 10, ex. 7 + 9
+ if (carry == 1) then
+ table.insert(result.digits, 1, 1)
+ end
+
+ return result
+
+end
+
+-- BACKEND: Subtract big2 from big1, ignoring signs
+function bigint.subtract_raw(big1, big2)
+ -- Type checking is done by bigint.compare
+ assert(bigint.compare(bigint.abs(big1), bigint.abs(big2), ">="),
+ "Size of " .. bigint.unserialize(big1, "string") .. " is less than "
+ .. bigint.unserialize(big2, "string"))
+
+ local result = big1:clone()
+ local max_digits = #big1.digits
+ local borrow = 0
+
+ -- Logic mostly copied from bigint.add_raw ---------------------------------
+ -- Walk backwards right to left, like in long subtraction
+ for digit = 0, max_digits - 1 do
+ local diff = (big1.digits[#big1.digits - digit] or 0)
+ - (big2.digits[#big2.digits - digit] or 0)
+ - borrow
+
+ if (diff < 0) then
+ borrow = 1
+ diff = diff + 10
+ else
+ borrow = 0
+ end
+
+ result.digits[max_digits - digit] = diff
+ end
+ ----------------------------------------------------------------------------
+
+
+ -- Strip leading zeroes if any, but not if 0 is the only digit
+ while (#result.digits > 1) and (result.digits[1] == 0) do
+ table.remove(result.digits, 1)
+ end
+
+ return result
+end
+
+-- FRONTEND: Addition and subtraction operations, accounting for signs
+function bigint.add(big1, big2)
+ -- Type checking is done by bigint.compare
+
+ local result
+
+ -- If adding numbers of different sign, subtract the smaller sized one from
+ -- the bigger sized one and take the sign of the bigger sized one
+ if (big1.sign ~= big2.sign) then
+ if (bigint.compare(bigint.abs(big1), bigint.abs(big2), ">")) then
+ result = bigint.subtract_raw(big1, big2)
+ result.sign = big1.sign
+ else
+ result = bigint.subtract_raw(big2, big1)
+ result.sign = big2.sign
+ end
+
+ elseif (big1.sign == "+") and (big2.sign == "+") then
+ result = bigint.add_raw(big1, big2)
+
+ elseif (big1.sign == "-") and (big2.sign == "-") then
+ result = bigint.add_raw(big1, big2)
+ result.sign = "-"
+ end
+
+ return result
+end
+function bigint.subtract(big1, big2)
+ -- Type checking is done by bigint.compare in bigint.add
+ -- Subtracting is like adding a negative
+ local big2_local = big2:clone()
+ if (big2.sign == "+") then
+ big2_local.sign = "-"
+ else
+ big2_local.sign = "+"
+ end
+ return bigint.add(big1, big2_local)
+end
+
+-- BACKEND: Multiply a big by a single digit big, ignoring signs
+function bigint.multiply_single(big1, big2)
+ bigint.check(big1)
+ bigint.check(big2)
+ assert(#big2.digits == 1, bigint.unserialize(big2, "string")
+ .. " has more than one digit")
+
+ local result = bigint.new()
+ local carry = 0
+
+ -- Logic mostly copied from bigint.add_raw ---------------------------------
+ -- Walk backwards right to left, like in long multiplication
+ for digit = 0, #big1.digits - 1 do
+ local this_digit = big1.digits[#big1.digits - digit]
+ * big2.digits[1]
+ + carry
+
+ if (this_digit >= 10) then
+ carry = math.floor(this_digit / 10)
+ this_digit = this_digit - (carry * 10)
+ else
+ carry = 0
+ end
+
+ result.digits[#big1.digits - digit] = this_digit
+ end
+
+ -- Leftover carry in cases when big1.digits[1] * big2.digits[1] > 0
+ if (carry > 0) then
+ table.insert(result.digits, 1, carry)
+ end
+ ----------------------------------------------------------------------------
+
+ return result
+end
+
+-- FRONTEND: Multiply two bigs, accounting for signs
+function bigint.multiply(big1, big2)
+ -- Type checking done by bigint.multiply_single
+
+ local result = bigint.new(0)
+ local larger, smaller -- Larger and smaller in terms of digits, not size
+
+ if (bigint.unserialize(big1) == 0) or (bigint.unserialize(big2) == 0) then
+ return result
+ end
+
+ if (#big1.digits >= #big2.digits) then
+ larger = big1
+ smaller = big2
+ else
+ larger = big2
+ smaller = big1
+ end
+
+ -- Walk backwards right to left, like in long multiplication
+ for digit = 0, #smaller.digits - 1 do
+ -- Sorry for going over column 80! There's lots of big names here
+ local this_digit_product = bigint.multiply_single(larger,
+ bigint.new(smaller.digits[#smaller.digits - digit]))
+
+ -- "Placeholding zeroes"
+ if (digit > 0) then
+ for placeholder = 1, digit do
+ table.insert(this_digit_product.digits, 0)
+ end
+ end
+
+ result = bigint.add(result, this_digit_product)
+ end
+
+ if (larger.sign == smaller.sign) then
+ result.sign = "+"
+ else
+ result.sign = "-"
+ end
+
+ return result
+end
+
+-- Raise a big to a positive integer or big power (TODO: negative integer power)
+function bigint.exponentiate(big, power)
+ -- Type checking for big done by bigint.multiply
+ assert(bigint.compare(power, bigint.new(0), ">="),
+ " negative powers are not supported")
+ local exp = power:clone()
+
+ if (bigint.compare(exp, bigint.new(0), "==")) then
+ return bigint.new(1)
+ elseif (bigint.compare(exp, bigint.new(1), "==")) then
+ return big:clone()
+ else
+ local result = bigint.new(1)
+ local base = big:clone()
+
+ while (true) do
+ if (bigint.compare(
+ bigint.modulus(exp, bigint.new(2)), bigint.new(1), "=="
+ )) then
+ result = bigint.multiply(result, base)
+ end
+ if (bigint.compare(exp, bigint.new(1), "==")) then
+ break
+ else
+ exp = bigint.divide(exp, bigint.new(2))
+ base = bigint.multiply(base, base)
+ end
+ end
+
+ return result
+ end
+
+end
+
+-- BACKEND: Divide two bigs (decimals not supported), returning big result and
+-- big remainder
+-- WARNING: Only supports positive integers
+function bigint.divide_raw(big1, big2)
+ -- Type checking done by bigint.compare
+ if (bigint.compare(big1, big2, "==")) then
+ return bigint.new(1), bigint.new(0)
+ elseif (bigint.compare(big1, big2, "<")) then
+ return bigint.new(0), big1:clone()
+ else
+ assert(bigint.compare(big2, bigint.new(0), "!="), "error: divide by zero")
+ assert(big1.sign == "+", "error: big1 is not positive")
+ assert(big2.sign == "+", "error: big2 is not positive")
+
+ local result = bigint.new()
+
+ local dividend = bigint.new() -- Dividend of a single operation
+
+ local neg_zero = bigint.new(0)
+ neg_zero.sign = "-"
+
+ for i = 1, #big1.digits do
+ -- Fixes a negative zero bug
+ if (#dividend.digits ~= 0) and (bigint.compare(dividend, neg_zero, "==")) then
+ dividend = bigint.new()
+ end
+
+ table.insert(dividend.digits, big1.digits[i])
+
+ local factor = bigint.new(0)
+ while bigint.compare(dividend, big2, ">=") do
+ dividend = bigint.subtract(dividend, big2)
+ factor = bigint.add(factor, bigint.new(1))
+ end
+
+ for i = 0, #factor.digits - 1 do
+ result.digits[#result.digits + 1 - i] = factor.digits[i + 1]
+ end
+ end
+
+ -- Remove leading zeros from result
+ while (result.digits[1] == 0) do
+ table.remove(result.digits, 1)
+ end
+
+ return result, dividend
+ end
+end
+
+-- FRONTEND: Divide two bigs (decimals not supported), returning big result and
+-- big remainder, accounting for signs
+function bigint.divide(big1, big2)
+ local result, remainder = bigint.divide_raw(bigint.abs(big1),
+ bigint.abs(big2))
+ if (big1.sign == big2.sign) then
+ result.sign = "+"
+ else
+ result.sign = "-"
+ end
+
+ return result, remainder
+end
+
+-- FRONTEND: Return only the remainder from bigint.divide
+function bigint.modulus(big1, big2)
+ local result, remainder = bigint.divide(big1, big2)
+
+ -- Remainder will always have the same sign as the dividend per C standard
+ -- https://en.wikipedia.org/wiki/Modulo_operation#Remainder_calculation_for_the_modulo_operation
+ remainder.sign = big1.sign
+ return remainder
+end
+
+return bigint
diff --git a/libs/bigint/named-powers-of-ten.lua b/libs/bigint/named-powers-of-ten.lua
new file mode 100644
index 0000000..aaa81ef
--- /dev/null
+++ b/libs/bigint/named-powers-of-ten.lua
@@ -0,0 +1,3340 @@
+-- Generated by util/isthe.com-number-names.sh
+
+number_names = {}
+
+number_names[2] = "hundred"
+number_names[3] = "thousand"
+number_names[6] = "million"
+number_names[9] = "billion"
+number_names[12] = "trillion"
+number_names[15] = "quadrillion"
+number_names[18] = "quintillion"
+number_names[21] = "sextillion"
+number_names[24] = "septillion"
+number_names[27] = "octillion"
+number_names[30] = "nonillion"
+number_names[33] = "decillion"
+number_names[36] = "undecillion"
+number_names[39] = "duodecillion"
+number_names[42] = "tredecillion"
+number_names[45] = "quattuordecillion"
+number_names[48] = "quindecillion"
+number_names[51] = "sexdecillion"
+number_names[54] = "septendecillion"
+number_names[57] = "octadecillion"
+number_names[60] = "novemdecillion"
+number_names[63] = "vigintillion"
+number_names[66] = "unvigintillion"
+number_names[69] = "duovigintillion"
+number_names[72] = "trevigintillion"
+number_names[75] = "quattuorvigintillion"
+number_names[78] = "quinvigintillion"
+number_names[81] = "sexvigintillion"
+number_names[84] = "septenvigintillion"
+number_names[87] = "octavigintillion"
+number_names[90] = "novemvigintillion"
+number_names[93] = "trigintillion"
+number_names[96] = "untrigintillion"
+number_names[99] = "duotrigintillion"
+number_names[102] = "tretrigintillion"
+number_names[105] = "quattuortrigintillion"
+number_names[108] = "quintrigintillion"
+number_names[111] = "sextrigintillion"
+number_names[114] = "septentrigintillion"
+number_names[117] = "octatrigintillion"
+number_names[120] = "novemtrigintillion"
+number_names[123] = "quadragintillion"
+number_names[126] = "unquadragintillion"
+number_names[129] = "duoquadragintillion"
+number_names[132] = "trequadragintillion"
+number_names[135] = "quattuorquadragintillion"
+number_names[138] = "quinquadragintillion"
+number_names[141] = "sexquadragintillion"
+number_names[144] = "septenquadragintillion"
+number_names[147] = "octaquadragintillion"
+number_names[150] = "novemquadragintillion"
+number_names[153] = "quinquagintillion"
+number_names[156] = "unquinquagintillion"
+number_names[159] = "duoquinquagintillion"
+number_names[162] = "trequinquagintillion"
+number_names[165] = "quattuorquinquagintillion"
+number_names[168] = "quinquinquagintillion"
+number_names[171] = "sexquinquagintillion"
+number_names[174] = "septenquinquagintillion"
+number_names[177] = "octaquinquagintillion"
+number_names[180] = "novemquinquagintillion"
+number_names[183] = "sexagintillion"
+number_names[186] = "unsexagintillion"
+number_names[189] = "duosexagintillion"
+number_names[192] = "tresexagintillion"
+number_names[195] = "quattuorsexagintillion"
+number_names[198] = "quinsexagintillion"
+number_names[201] = "sexsexagintillion"
+number_names[204] = "septensexagintillion"
+number_names[207] = "octasexagintillion"
+number_names[210] = "novemsexagintillion"
+number_names[213] = "septuagintillion"
+number_names[216] = "unseptuagintillion"
+number_names[219] = "duoseptuagintillion"
+number_names[222] = "treseptuagintillion"
+number_names[225] = "quattuorseptuagintillion"
+number_names[228] = "quinseptuagintillion"
+number_names[231] = "sexseptuagintillion"
+number_names[234] = "septenseptuagintillion"
+number_names[237] = "octaseptuagintillion"
+number_names[240] = "novemseptuagintillion"
+number_names[243] = "octagintillion"
+number_names[246] = "unoctogintillion"
+number_names[249] = "duooctogintillion"
+number_names[252] = "treoctogintillion"
+number_names[255] = "quattuoroctogintillion"
+number_names[258] = "quinoctogintillion"
+number_names[261] = "sexoctogintillion"
+number_names[264] = "septenoctogintillion"
+number_names[267] = "octaoctogintillion"
+number_names[270] = "novemoctogintillion"
+number_names[273] = "nonagintillion"
+number_names[276] = "unnonagintillion"
+number_names[279] = "duononagintillion"
+number_names[282] = "trenonagintillion"
+number_names[285] = "quattuornonagintillion"
+number_names[288] = "quinnonagintillion"
+number_names[291] = "sexnonagintillion"
+number_names[294] = "septennonagintillion"
+number_names[297] = "octanonagintillion"
+number_names[300] = "novemnonagintillion"
+number_names[303] = "centillion"
+number_names[306] = "cenuntillion"
+number_names[309] = "cendotillion"
+number_names[312] = "centretillion"
+number_names[315] = "cenquattuortillion"
+number_names[318] = "cenquintillion"
+number_names[321] = "censextillion"
+number_names[324] = "censeptentillion"
+number_names[327] = "cenoctotillion"
+number_names[330] = "cennovemtillion"
+number_names[333] = "cendecillion"
+number_names[336] = "cenundecillion"
+number_names[339] = "cendodecillion"
+number_names[342] = "centredecillion"
+number_names[345] = "cenquattuordecillion"
+number_names[348] = "cenquindecillion"
+number_names[351] = "censexdecillion"
+number_names[354] = "censeptendecillion"
+number_names[357] = "cenoctodecillion"
+number_names[360] = "cennovemdecillion"
+number_names[363] = "cenvigintillion"
+number_names[366] = "cenunvigintillion"
+number_names[369] = "cendovigintillion"
+number_names[372] = "centrevigintillion"
+number_names[375] = "cenquattuorvigintillion"
+number_names[378] = "cenquinvigintillion"
+number_names[381] = "censexvigintillion"
+number_names[384] = "censeptenvigintillion"
+number_names[387] = "cenoctovigintillion"
+number_names[390] = "cennovemvigintillion"
+number_names[393] = "centrigintillion"
+number_names[396] = "cenuntrigintillion"
+number_names[399] = "cendotrigintillion"
+number_names[402] = "centretrigintillion"
+number_names[405] = "cenquattuortrigintillion"
+number_names[408] = "cenquintrigintillion"
+number_names[411] = "censextrigintillion"
+number_names[414] = "censeptentrigintillion"
+number_names[417] = "cenoctotrigintillion"
+number_names[420] = "cennovemtrigintillion"
+number_names[423] = "cenquadragintillion"
+number_names[426] = "cenunquadragintillion"
+number_names[429] = "cendoquadragintillion"
+number_names[432] = "centrequadragintillion"
+number_names[435] = "cenquattuorquadragintillion"
+number_names[438] = "cenquinquadragintillion"
+number_names[441] = "censexquadragintillion"
+number_names[444] = "censeptenquadragintillion"
+number_names[447] = "cenoctoquadragintillion"
+number_names[450] = "cennovemquadragintillion"
+number_names[453] = "cenquinquagintillion"
+number_names[456] = "cenunquinquagintillion"
+number_names[459] = "cendoquinquagintillion"
+number_names[462] = "centrequinquagintillion"
+number_names[465] = "cenquattuorquinquagintillion"
+number_names[468] = "cenquinquinquagintillion"
+number_names[471] = "censexquinquagintillion"
+number_names[474] = "censeptenquinquagintillion"
+number_names[477] = "cenoctoquinquagintillion"
+number_names[480] = "cennovemquinquagintillion"
+number_names[483] = "censexagintillion"
+number_names[486] = "cenunsexagintillion"
+number_names[489] = "cendosexagintillion"
+number_names[492] = "centresexagintillion"
+number_names[495] = "cenquattuorsexagintillion"
+number_names[498] = "cenquinsexagintillion"
+number_names[501] = "censexsexagintillion"
+number_names[504] = "censeptensexagintillion"
+number_names[507] = "cenoctosexagintillion"
+number_names[510] = "cennovemsexagintillion"
+number_names[513] = "censeptuagintillion"
+number_names[516] = "cenunseptuagintillion"
+number_names[519] = "cendoseptuagintillion"
+number_names[522] = "centreseptuagintillion"
+number_names[525] = "cenquattuorseptuagintillion"
+number_names[528] = "cenquinseptuagintillion"
+number_names[531] = "censexseptuagintillion"
+number_names[534] = "censeptenseptuagintillion"
+number_names[537] = "cenoctoseptuagintillion"
+number_names[540] = "cennovemseptuagintillion"
+number_names[543] = "cenoctogintillion"
+number_names[546] = "cenunoctogintillion"
+number_names[549] = "cendooctogintillion"
+number_names[552] = "centreoctogintillion"
+number_names[555] = "cenquattuoroctogintillion"
+number_names[558] = "cenquinoctogintillion"
+number_names[561] = "censexoctogintillion"
+number_names[564] = "censeptenoctogintillion"
+number_names[567] = "cenoctooctogintillion"
+number_names[570] = "cennovemoctogintillion"
+number_names[573] = "cennonagintillion"
+number_names[576] = "cenunnonagintillion"
+number_names[579] = "cendononagintillion"
+number_names[582] = "centrenonagintillion"
+number_names[585] = "cenquattuornonagintillion"
+number_names[588] = "cenquinnonagintillion"
+number_names[591] = "censexnonagintillion"
+number_names[594] = "censeptennonagintillion"
+number_names[597] = "cenoctononagintillion"
+number_names[600] = "cennovemnonagintillion"
+number_names[603] = "duocentillion"
+number_names[606] = "duocenuntillion"
+number_names[609] = "duocendotillion"
+number_names[612] = "duocentretillion"
+number_names[615] = "duocenquattuortillion"
+number_names[618] = "duocenquintillion"
+number_names[621] = "duocensextillion"
+number_names[624] = "duocenseptentillion"
+number_names[627] = "duocenoctotillion"
+number_names[630] = "duocennovemtillion"
+number_names[633] = "duocendecillion"
+number_names[636] = "duocenundecillion"
+number_names[639] = "duocendodecillion"
+number_names[642] = "duocentredecillion"
+number_names[645] = "duocenquattuordecillion"
+number_names[648] = "duocenquindecillion"
+number_names[651] = "duocensexdecillion"
+number_names[654] = "duocenseptendecillion"
+number_names[657] = "duocenoctodecillion"
+number_names[660] = "duocennovemdecillion"
+number_names[663] = "duocenvigintillion"
+number_names[666] = "duocenunvigintillion"
+number_names[669] = "duocendovigintillion"
+number_names[672] = "duocentrevigintillion"
+number_names[675] = "duocenquattuorvigintillion"
+number_names[678] = "duocenquinvigintillion"
+number_names[681] = "duocensexvigintillion"
+number_names[684] = "duocenseptenvigintillion"
+number_names[687] = "duocenoctovigintillion"
+number_names[690] = "duocennovemvigintillion"
+number_names[693] = "duocentrigintillion"
+number_names[696] = "duocenuntrigintillion"
+number_names[699] = "duocendotrigintillion"
+number_names[702] = "duocentretrigintillion"
+number_names[705] = "duocenquattuortrigintillion"
+number_names[708] = "duocenquintrigintillion"
+number_names[711] = "duocensextrigintillion"
+number_names[714] = "duocenseptentrigintillion"
+number_names[717] = "duocenoctotrigintillion"
+number_names[720] = "duocennovemtrigintillion"
+number_names[723] = "duocenquadragintillion"
+number_names[726] = "duocenunquadragintillion"
+number_names[729] = "duocendoquadragintillion"
+number_names[732] = "duocentrequadragintillion"
+number_names[735] = "duocenquattuorquadragintillion"
+number_names[738] = "duocenquinquadragintillion"
+number_names[741] = "duocensexquadragintillion"
+number_names[744] = "duocenseptenquadragintillion"
+number_names[747] = "duocenoctoquadragintillion"
+number_names[750] = "duocennovemquadragintillion"
+number_names[753] = "duocenquinquagintillion"
+number_names[756] = "duocenunquinquagintillion"
+number_names[759] = "duocendoquinquagintillion"
+number_names[762] = "duocentrequinquagintillion"
+number_names[765] = "duocenquattuorquinquagintillion"
+number_names[768] = "duocenquinquinquagintillion"
+number_names[771] = "duocensexquinquagintillion"
+number_names[774] = "duocenseptenquinquagintillion"
+number_names[777] = "duocenoctoquinquagintillion"
+number_names[780] = "duocennovemquinquagintillion"
+number_names[783] = "duocensexagintillion"
+number_names[786] = "duocenunsexagintillion"
+number_names[789] = "duocendosexagintillion"
+number_names[792] = "duocentresexagintillion"
+number_names[795] = "duocenquattuorsexagintillion"
+number_names[798] = "duocenquinsexagintillion"
+number_names[801] = "duocensexsexagintillion"
+number_names[804] = "duocenseptensexagintillion"
+number_names[807] = "duocenoctosexagintillion"
+number_names[810] = "duocennovemsexagintillion"
+number_names[813] = "duocenseptuagintillion"
+number_names[816] = "duocenunseptuagintillion"
+number_names[819] = "duocendoseptuagintillion"
+number_names[822] = "duocentreseptuagintillion"
+number_names[825] = "duocenquattuorseptuagintillion"
+number_names[828] = "duocenquinseptuagintillion"
+number_names[831] = "duocensexseptuagintillion"
+number_names[834] = "duocenseptenseptuagintillion"
+number_names[837] = "duocenoctoseptuagintillion"
+number_names[840] = "duocennovemseptuagintillion"
+number_names[843] = "duocenoctogintillion"
+number_names[846] = "duocenunoctogintillion"
+number_names[849] = "duocendooctogintillion"
+number_names[852] = "duocentreoctogintillion"
+number_names[855] = "duocenquattuoroctogintillion"
+number_names[858] = "duocenquinoctogintillion"
+number_names[861] = "duocensexoctogintillion"
+number_names[864] = "duocenseptenoctogintillion"
+number_names[867] = "duocenoctooctogintillion"
+number_names[870] = "duocennovemoctogintillion"
+number_names[873] = "duocennonagintillion"
+number_names[876] = "duocenunnonagintillion"
+number_names[879] = "duocendononagintillion"
+number_names[882] = "duocentrenonagintillion"
+number_names[885] = "duocenquattuornonagintillion"
+number_names[888] = "duocenquinnonagintillion"
+number_names[891] = "duocensexnonagintillion"
+number_names[894] = "duocenseptennonagintillion"
+number_names[897] = "duocenoctononagintillion"
+number_names[900] = "duocennovemnonagintillion"
+number_names[903] = "trecentillion"
+number_names[906] = "trecenuntillion"
+number_names[909] = "trecendotillion"
+number_names[912] = "trecentretillion"
+number_names[915] = "trecenquattuortillion"
+number_names[918] = "trecenquintillion"
+number_names[921] = "trecensextillion"
+number_names[924] = "trecenseptentillion"
+number_names[927] = "trecenoctotillion"
+number_names[930] = "trecennovemtillion"
+number_names[933] = "trecendecillion"
+number_names[936] = "trecenundecillion"
+number_names[939] = "trecendodecillion"
+number_names[942] = "trecentredecillion"
+number_names[945] = "trecenquattuordecillion"
+number_names[948] = "trecenquindecillion"
+number_names[951] = "trecensexdecillion"
+number_names[954] = "trecenseptendecillion"
+number_names[957] = "trecenoctodecillion"
+number_names[960] = "trecennovemdecillion"
+number_names[963] = "trecenvigintillion"
+number_names[966] = "trecenunvigintillion"
+number_names[969] = "trecendovigintillion"
+number_names[972] = "trecentrevigintillion"
+number_names[975] = "trecenquattuorvigintillion"
+number_names[978] = "trecenquinvigintillion"
+number_names[981] = "trecensexvigintillion"
+number_names[984] = "trecenseptenvigintillion"
+number_names[987] = "trecenoctovigintillion"
+number_names[990] = "trecennovemvigintillion"
+number_names[993] = "trecentrigintillion"
+number_names[996] = "trecenuntrigintillion"
+number_names[999] = "trecendotrigintillion"
+number_names[1002] = "trecentretrigintillion"
+number_names[1005] = "trecenquattuortrigintillion"
+number_names[1008] = "trecenquintrigintillion"
+number_names[1011] = "trecensextrigintillion"
+number_names[1014] = "trecenseptentrigintillion"
+number_names[1017] = "trecenoctotrigintillion"
+number_names[1020] = "trecennovemtrigintillion"
+number_names[1023] = "trecenquadragintillion"
+number_names[1026] = "trecenunquadragintillion"
+number_names[1029] = "trecendoquadragintillion"
+number_names[1032] = "trecentrequadragintillion"
+number_names[1035] = "trecenquattuorquadragintillion"
+number_names[1038] = "trecenquinquadragintillion"
+number_names[1041] = "trecensexquadragintillion"
+number_names[1044] = "trecenseptenquadragintillion"
+number_names[1047] = "trecenoctoquadragintillion"
+number_names[1050] = "trecennovemquadragintillion"
+number_names[1053] = "trecenquinquagintillion"
+number_names[1056] = "trecenunquinquagintillion"
+number_names[1059] = "trecendoquinquagintillion"
+number_names[1062] = "trecentrequinquagintillion"
+number_names[1065] = "trecenquattuorquinquagintillion"
+number_names[1068] = "trecenquinquinquagintillion"
+number_names[1071] = "trecensexquinquagintillion"
+number_names[1074] = "trecenseptenquinquagintillion"
+number_names[1077] = "trecenoctoquinquagintillion"
+number_names[1080] = "trecennovemquinquagintillion"
+number_names[1083] = "trecensexagintillion"
+number_names[1086] = "trecenunsexagintillion"
+number_names[1089] = "trecendosexagintillion"
+number_names[1092] = "trecentresexagintillion"
+number_names[1095] = "trecenquattuorsexagintillion"
+number_names[1098] = "trecenquinsexagintillion"
+number_names[1101] = "trecensexsexagintillion"
+number_names[1104] = "trecenseptensexagintillion"
+number_names[1107] = "trecenoctosexagintillion"
+number_names[1110] = "trecennovemsexagintillion"
+number_names[1113] = "trecenseptuagintillion"
+number_names[1116] = "trecenunseptuagintillion"
+number_names[1119] = "trecendoseptuagintillion"
+number_names[1122] = "trecentreseptuagintillion"
+number_names[1125] = "trecenquattuorseptuagintillion"
+number_names[1128] = "trecenquinseptuagintillion"
+number_names[1131] = "trecensexseptuagintillion"
+number_names[1134] = "trecenseptenseptuagintillion"
+number_names[1137] = "trecenoctoseptuagintillion"
+number_names[1140] = "trecennovemseptuagintillion"
+number_names[1143] = "trecenoctogintillion"
+number_names[1146] = "trecenunoctogintillion"
+number_names[1149] = "trecendooctogintillion"
+number_names[1152] = "trecentreoctogintillion"
+number_names[1155] = "trecenquattuoroctogintillion"
+number_names[1158] = "trecenquinoctogintillion"
+number_names[1161] = "trecensexoctogintillion"
+number_names[1164] = "trecenseptenoctogintillion"
+number_names[1167] = "trecenoctooctogintillion"
+number_names[1170] = "trecennovemoctogintillion"
+number_names[1173] = "trecennonagintillion"
+number_names[1176] = "trecenunnonagintillion"
+number_names[1179] = "trecendononagintillion"
+number_names[1182] = "trecentrenonagintillion"
+number_names[1185] = "trecenquattuornonagintillion"
+number_names[1188] = "trecenquinnonagintillion"
+number_names[1191] = "trecensexnonagintillion"
+number_names[1194] = "trecenseptennonagintillion"
+number_names[1197] = "trecenoctononagintillion"
+number_names[1200] = "trecennovemnonagintillion"
+number_names[1203] = "quadringentillion"
+number_names[1206] = "quadringenuntillion"
+number_names[1209] = "quadringendotillion"
+number_names[1212] = "quadringentretillion"
+number_names[1215] = "quadringenquattuortillion"
+number_names[1218] = "quadringenquintillion"
+number_names[1221] = "quadringensextillion"
+number_names[1224] = "quadringenseptentillion"
+number_names[1227] = "quadringenoctotillion"
+number_names[1230] = "quadringennovemtillion"
+number_names[1233] = "quadringendecillion"
+number_names[1236] = "quadringenundecillion"
+number_names[1239] = "quadringendodecillion"
+number_names[1242] = "quadringentredecillion"
+number_names[1245] = "quadringenquattuordecillion"
+number_names[1248] = "quadringenquindecillion"
+number_names[1251] = "quadringensexdecillion"
+number_names[1254] = "quadringenseptendecillion"
+number_names[1257] = "quadringenoctodecillion"
+number_names[1260] = "quadringennovemdecillion"
+number_names[1263] = "quadringenvigintillion"
+number_names[1266] = "quadringenunvigintillion"
+number_names[1269] = "quadringendovigintillion"
+number_names[1272] = "quadringentrevigintillion"
+number_names[1275] = "quadringenquattuorvigintillion"
+number_names[1278] = "quadringenquinvigintillion"
+number_names[1281] = "quadringensexvigintillion"
+number_names[1284] = "quadringenseptenvigintillion"
+number_names[1287] = "quadringenoctovigintillion"
+number_names[1290] = "quadringennovemvigintillion"
+number_names[1293] = "quadringentrigintillion"
+number_names[1296] = "quadringenuntrigintillion"
+number_names[1299] = "quadringendotrigintillion"
+number_names[1302] = "quadringentretrigintillion"
+number_names[1305] = "quadringenquattuortrigintillion"
+number_names[1308] = "quadringenquintrigintillion"
+number_names[1311] = "quadringensextrigintillion"
+number_names[1314] = "quadringenseptentrigintillion"
+number_names[1317] = "quadringenoctotrigintillion"
+number_names[1320] = "quadringennovemtrigintillion"
+number_names[1323] = "quadringenquadragintillion"
+number_names[1326] = "quadringenunquadragintillion"
+number_names[1329] = "quadringendoquadragintillion"
+number_names[1332] = "quadringentrequadragintillion"
+number_names[1335] = "quadringenquattuorquadragintillion"
+number_names[1338] = "quadringenquinquadragintillion"
+number_names[1341] = "quadringensexquadragintillion"
+number_names[1344] = "quadringenseptenquadragintillion"
+number_names[1347] = "quadringenoctoquadragintillion"
+number_names[1350] = "quadringennovemquadragintillion"
+number_names[1353] = "quadringenquinquagintillion"
+number_names[1356] = "quadringenunquinquagintillion"
+number_names[1359] = "quadringendoquinquagintillion"
+number_names[1362] = "quadringentrequinquagintillion"
+number_names[1365] = "quadringenquattuorquinquagintillion"
+number_names[1368] = "quadringenquinquinquagintillion"
+number_names[1371] = "quadringensexquinquagintillion"
+number_names[1374] = "quadringenseptenquinquagintillion"
+number_names[1377] = "quadringenoctoquinquagintillion"
+number_names[1380] = "quadringennovemquinquagintillion"
+number_names[1383] = "quadringensexagintillion"
+number_names[1386] = "quadringenunsexagintillion"
+number_names[1389] = "quadringendosexagintillion"
+number_names[1392] = "quadringentresexagintillion"
+number_names[1395] = "quadringenquattuorsexagintillion"
+number_names[1398] = "quadringenquinsexagintillion"
+number_names[1401] = "quadringensexsexagintillion"
+number_names[1404] = "quadringenseptensexagintillion"
+number_names[1407] = "quadringenoctosexagintillion"
+number_names[1410] = "quadringennovemsexagintillion"
+number_names[1413] = "quadringenseptuagintillion"
+number_names[1416] = "quadringenunseptuagintillion"
+number_names[1419] = "quadringendoseptuagintillion"
+number_names[1422] = "quadringentreseptuagintillion"
+number_names[1425] = "quadringenquattuorseptuagintillion"
+number_names[1428] = "quadringenquinseptuagintillion"
+number_names[1431] = "quadringensexseptuagintillion"
+number_names[1434] = "quadringenseptenseptuagintillion"
+number_names[1437] = "quadringenoctoseptuagintillion"
+number_names[1440] = "quadringennovemseptuagintillion"
+number_names[1443] = "quadringenoctogintillion"
+number_names[1446] = "quadringenunoctogintillion"
+number_names[1449] = "quadringendooctogintillion"
+number_names[1452] = "quadringentreoctogintillion"
+number_names[1455] = "quadringenquattuoroctogintillion"
+number_names[1458] = "quadringenquinoctogintillion"
+number_names[1461] = "quadringensexoctogintillion"
+number_names[1464] = "quadringenseptenoctogintillion"
+number_names[1467] = "quadringenoctooctogintillion"
+number_names[1470] = "quadringennovemoctogintillion"
+number_names[1473] = "quadringennonagintillion"
+number_names[1476] = "quadringenunnonagintillion"
+number_names[1479] = "quadringendononagintillion"
+number_names[1482] = "quadringentrenonagintillion"
+number_names[1485] = "quadringenquattuornonagintillion"
+number_names[1488] = "quadringenquinnonagintillion"
+number_names[1491] = "quadringensexnonagintillion"
+number_names[1494] = "quadringenseptennonagintillion"
+number_names[1497] = "quadringenoctononagintillion"
+number_names[1500] = "quadringennovemnonagintillion"
+number_names[1503] = "quingentillion"
+number_names[1506] = "quingenuntillion"
+number_names[1509] = "quingendotillion"
+number_names[1512] = "quingentretillion"
+number_names[1515] = "quingenquattuortillion"
+number_names[1518] = "quingenquintillion"
+number_names[1521] = "quingensextillion"
+number_names[1524] = "quingenseptentillion"
+number_names[1527] = "quingenoctotillion"
+number_names[1530] = "quingennovemtillion"
+number_names[1533] = "quingendecillion"
+number_names[1536] = "quingenundecillion"
+number_names[1539] = "quingendodecillion"
+number_names[1542] = "quingentredecillion"
+number_names[1545] = "quingenquattuordecillion"
+number_names[1548] = "quingenquindecillion"
+number_names[1551] = "quingensexdecillion"
+number_names[1554] = "quingenseptendecillion"
+number_names[1557] = "quingenoctodecillion"
+number_names[1560] = "quingennovemdecillion"
+number_names[1563] = "quingenvigintillion"
+number_names[1566] = "quingenunvigintillion"
+number_names[1569] = "quingendovigintillion"
+number_names[1572] = "quingentrevigintillion"
+number_names[1575] = "quingenquattuorvigintillion"
+number_names[1578] = "quingenquinvigintillion"
+number_names[1581] = "quingensexvigintillion"
+number_names[1584] = "quingenseptenvigintillion"
+number_names[1587] = "quingenoctovigintillion"
+number_names[1590] = "quingennovemvigintillion"
+number_names[1593] = "quingentrigintillion"
+number_names[1596] = "quingenuntrigintillion"
+number_names[1599] = "quingendotrigintillion"
+number_names[1602] = "quingentretrigintillion"
+number_names[1605] = "quingenquattuortrigintillion"
+number_names[1608] = "quingenquintrigintillion"
+number_names[1611] = "quingensextrigintillion"
+number_names[1614] = "quingenseptentrigintillion"
+number_names[1617] = "quingenoctotrigintillion"
+number_names[1620] = "quingennovemtrigintillion"
+number_names[1623] = "quingenquadragintillion"
+number_names[1626] = "quingenunquadragintillion"
+number_names[1629] = "quingendoquadragintillion"
+number_names[1632] = "quingentrequadragintillion"
+number_names[1635] = "quingenquattuorquadragintillion"
+number_names[1638] = "quingenquinquadragintillion"
+number_names[1641] = "quingensexquadragintillion"
+number_names[1644] = "quingenseptenquadragintillion"
+number_names[1647] = "quingenoctoquadragintillion"
+number_names[1650] = "quingennovemquadragintillion"
+number_names[1653] = "quingenquinquagintillion"
+number_names[1656] = "quingenunquinquagintillion"
+number_names[1659] = "quingendoquinquagintillion"
+number_names[1662] = "quingentrequinquagintillion"
+number_names[1665] = "quingenquattuorquinquagintillion"
+number_names[1668] = "quingenquinquinquagintillion"
+number_names[1671] = "quingensexquinquagintillion"
+number_names[1674] = "quingenseptenquinquagintillion"
+number_names[1677] = "quingenoctoquinquagintillion"
+number_names[1680] = "quingennovemquinquagintillion"
+number_names[1683] = "quingensexagintillion"
+number_names[1686] = "quingenunsexagintillion"
+number_names[1689] = "quingendosexagintillion"
+number_names[1692] = "quingentresexagintillion"
+number_names[1695] = "quingenquattuorsexagintillion"
+number_names[1698] = "quingenquinsexagintillion"
+number_names[1701] = "quingensexsexagintillion"
+number_names[1704] = "quingenseptensexagintillion"
+number_names[1707] = "quingenoctosexagintillion"
+number_names[1710] = "quingennovemsexagintillion"
+number_names[1713] = "quingenseptuagintillion"
+number_names[1716] = "quingenunseptuagintillion"
+number_names[1719] = "quingendoseptuagintillion"
+number_names[1722] = "quingentreseptuagintillion"
+number_names[1725] = "quingenquattuorseptuagintillion"
+number_names[1728] = "quingenquinseptuagintillion"
+number_names[1731] = "quingensexseptuagintillion"
+number_names[1734] = "quingenseptenseptuagintillion"
+number_names[1737] = "quingenoctoseptuagintillion"
+number_names[1740] = "quingennovemseptuagintillion"
+number_names[1743] = "quingenoctogintillion"
+number_names[1746] = "quingenunoctogintillion"
+number_names[1749] = "quingendooctogintillion"
+number_names[1752] = "quingentreoctogintillion"
+number_names[1755] = "quingenquattuoroctogintillion"
+number_names[1758] = "quingenquinoctogintillion"
+number_names[1761] = "quingensexoctogintillion"
+number_names[1764] = "quingenseptenoctogintillion"
+number_names[1767] = "quingenoctooctogintillion"
+number_names[1770] = "quingennovemoctogintillion"
+number_names[1773] = "quingennonagintillion"
+number_names[1776] = "quingenunnonagintillion"
+number_names[1779] = "quingendononagintillion"
+number_names[1782] = "quingentrenonagintillion"
+number_names[1785] = "quingenquattuornonagintillion"
+number_names[1788] = "quingenquinnonagintillion"
+number_names[1791] = "quingensexnonagintillion"
+number_names[1794] = "quingenseptennonagintillion"
+number_names[1797] = "quingenoctononagintillion"
+number_names[1800] = "quingennovemnonagintillion"
+number_names[1803] = "sescentillion"
+number_names[1806] = "sescenuntillion"
+number_names[1809] = "sescendotillion"
+number_names[1812] = "sescentretillion"
+number_names[1815] = "sescenquattuortillion"
+number_names[1818] = "sescenquintillion"
+number_names[1821] = "sescensextillion"
+number_names[1824] = "sescenseptentillion"
+number_names[1827] = "sescenoctotillion"
+number_names[1830] = "sescennovemtillion"
+number_names[1833] = "sescendecillion"
+number_names[1836] = "sescenundecillion"
+number_names[1839] = "sescendodecillion"
+number_names[1842] = "sescentredecillion"
+number_names[1845] = "sescenquattuordecillion"
+number_names[1848] = "sescenquindecillion"
+number_names[1851] = "sescensexdecillion"
+number_names[1854] = "sescenseptendecillion"
+number_names[1857] = "sescenoctodecillion"
+number_names[1860] = "sescennovemdecillion"
+number_names[1863] = "sescenvigintillion"
+number_names[1866] = "sescenunvigintillion"
+number_names[1869] = "sescendovigintillion"
+number_names[1872] = "sescentrevigintillion"
+number_names[1875] = "sescenquattuorvigintillion"
+number_names[1878] = "sescenquinvigintillion"
+number_names[1881] = "sescensexvigintillion"
+number_names[1884] = "sescenseptenvigintillion"
+number_names[1887] = "sescenoctovigintillion"
+number_names[1890] = "sescennovemvigintillion"
+number_names[1893] = "sescentrigintillion"
+number_names[1896] = "sescenuntrigintillion"
+number_names[1899] = "sescendotrigintillion"
+number_names[1902] = "sescentretrigintillion"
+number_names[1905] = "sescenquattuortrigintillion"
+number_names[1908] = "sescenquintrigintillion"
+number_names[1911] = "sescensextrigintillion"
+number_names[1914] = "sescenseptentrigintillion"
+number_names[1917] = "sescenoctotrigintillion"
+number_names[1920] = "sescennovemtrigintillion"
+number_names[1923] = "sescenquadragintillion"
+number_names[1926] = "sescenunquadragintillion"
+number_names[1929] = "sescendoquadragintillion"
+number_names[1932] = "sescentrequadragintillion"
+number_names[1935] = "sescenquattuorquadragintillion"
+number_names[1938] = "sescenquinquadragintillion"
+number_names[1941] = "sescensexquadragintillion"
+number_names[1944] = "sescenseptenquadragintillion"
+number_names[1947] = "sescenoctoquadragintillion"
+number_names[1950] = "sescennovemquadragintillion"
+number_names[1953] = "sescenquinquagintillion"
+number_names[1956] = "sescenunquinquagintillion"
+number_names[1959] = "sescendoquinquagintillion"
+number_names[1962] = "sescentrequinquagintillion"
+number_names[1965] = "sescenquattuorquinquagintillion"
+number_names[1968] = "sescenquinquinquagintillion"
+number_names[1971] = "sescensexquinquagintillion"
+number_names[1974] = "sescenseptenquinquagintillion"
+number_names[1977] = "sescenoctoquinquagintillion"
+number_names[1980] = "sescennovemquinquagintillion"
+number_names[1983] = "sescensexagintillion"
+number_names[1986] = "sescenunsexagintillion"
+number_names[1989] = "sescendosexagintillion"
+number_names[1992] = "sescentresexagintillion"
+number_names[1995] = "sescenquattuorsexagintillion"
+number_names[1998] = "sescenquinsexagintillion"
+number_names[2001] = "sescensexsexagintillion"
+number_names[2004] = "sescenseptensexagintillion"
+number_names[2007] = "sescenoctosexagintillion"
+number_names[2010] = "sescennovemsexagintillion"
+number_names[2013] = "sescenseptuagintillion"
+number_names[2016] = "sescenunseptuagintillion"
+number_names[2019] = "sescendoseptuagintillion"
+number_names[2022] = "sescentreseptuagintillion"
+number_names[2025] = "sescenquattuorseptuagintillion"
+number_names[2028] = "sescenquinseptuagintillion"
+number_names[2031] = "sescensexseptuagintillion"
+number_names[2034] = "sescenseptenseptuagintillion"
+number_names[2037] = "sescenoctoseptuagintillion"
+number_names[2040] = "sescennovemseptuagintillion"
+number_names[2043] = "sescenoctogintillion"
+number_names[2046] = "sescenunoctogintillion"
+number_names[2049] = "sescendooctogintillion"
+number_names[2052] = "sescentreoctogintillion"
+number_names[2055] = "sescenquattuoroctogintillion"
+number_names[2058] = "sescenquinoctogintillion"
+number_names[2061] = "sescensexoctogintillion"
+number_names[2064] = "sescenseptenoctogintillion"
+number_names[2067] = "sescenoctooctogintillion"
+number_names[2070] = "sescennovemoctogintillion"
+number_names[2073] = "sescennonagintillion"
+number_names[2076] = "sescenunnonagintillion"
+number_names[2079] = "sescendononagintillion"
+number_names[2082] = "sescentrenonagintillion"
+number_names[2085] = "sescenquattuornonagintillion"
+number_names[2088] = "sescenquinnonagintillion"
+number_names[2091] = "sescensexnonagintillion"
+number_names[2094] = "sescenseptennonagintillion"
+number_names[2097] = "sescenoctononagintillion"
+number_names[2100] = "sescennovemnonagintillion"
+number_names[2103] = "septingentillion"
+number_names[2106] = "septingenuntillion"
+number_names[2109] = "septingendotillion"
+number_names[2112] = "septingentretillion"
+number_names[2115] = "septingenquattuortillion"
+number_names[2118] = "septingenquintillion"
+number_names[2121] = "septingensextillion"
+number_names[2124] = "septingenseptentillion"
+number_names[2127] = "septingenoctotillion"
+number_names[2130] = "septingennovemtillion"
+number_names[2133] = "septingendecillion"
+number_names[2136] = "septingenundecillion"
+number_names[2139] = "septingendodecillion"
+number_names[2142] = "septingentredecillion"
+number_names[2145] = "septingenquattuordecillion"
+number_names[2148] = "septingenquindecillion"
+number_names[2151] = "septingensexdecillion"
+number_names[2154] = "septingenseptendecillion"
+number_names[2157] = "septingenoctodecillion"
+number_names[2160] = "septingennovemdecillion"
+number_names[2163] = "septingenvigintillion"
+number_names[2166] = "septingenunvigintillion"
+number_names[2169] = "septingendovigintillion"
+number_names[2172] = "septingentrevigintillion"
+number_names[2175] = "septingenquattuorvigintillion"
+number_names[2178] = "septingenquinvigintillion"
+number_names[2181] = "septingensexvigintillion"
+number_names[2184] = "septingenseptenvigintillion"
+number_names[2187] = "septingenoctovigintillion"
+number_names[2190] = "septingennovemvigintillion"
+number_names[2193] = "septingentrigintillion"
+number_names[2196] = "septingenuntrigintillion"
+number_names[2199] = "septingendotrigintillion"
+number_names[2202] = "septingentretrigintillion"
+number_names[2205] = "septingenquattuortrigintillion"
+number_names[2208] = "septingenquintrigintillion"
+number_names[2211] = "septingensextrigintillion"
+number_names[2214] = "septingenseptentrigintillion"
+number_names[2217] = "septingenoctotrigintillion"
+number_names[2220] = "septingennovemtrigintillion"
+number_names[2223] = "septingenquadragintillion"
+number_names[2226] = "septingenunquadragintillion"
+number_names[2229] = "septingendoquadragintillion"
+number_names[2232] = "septingentrequadragintillion"
+number_names[2235] = "septingenquattuorquadragintillion"
+number_names[2238] = "septingenquinquadragintillion"
+number_names[2241] = "septingensexquadragintillion"
+number_names[2244] = "septingenseptenquadragintillion"
+number_names[2247] = "septingenoctoquadragintillion"
+number_names[2250] = "septingennovemquadragintillion"
+number_names[2253] = "septingenquinquagintillion"
+number_names[2256] = "septingenunquinquagintillion"
+number_names[2259] = "septingendoquinquagintillion"
+number_names[2262] = "septingentrequinquagintillion"
+number_names[2265] = "septingenquattuorquinquagintillion"
+number_names[2268] = "septingenquinquinquagintillion"
+number_names[2271] = "septingensexquinquagintillion"
+number_names[2274] = "septingenseptenquinquagintillion"
+number_names[2277] = "septingenoctoquinquagintillion"
+number_names[2280] = "septingennovemquinquagintillion"
+number_names[2283] = "septingensexagintillion"
+number_names[2286] = "septingenunsexagintillion"
+number_names[2289] = "septingendosexagintillion"
+number_names[2292] = "septingentresexagintillion"
+number_names[2295] = "septingenquattuorsexagintillion"
+number_names[2298] = "septingenquinsexagintillion"
+number_names[2301] = "septingensexsexagintillion"
+number_names[2304] = "septingenseptensexagintillion"
+number_names[2307] = "septingenoctosexagintillion"
+number_names[2310] = "septingennovemsexagintillion"
+number_names[2313] = "septingenseptuagintillion"
+number_names[2316] = "septingenunseptuagintillion"
+number_names[2319] = "septingendoseptuagintillion"
+number_names[2322] = "septingentreseptuagintillion"
+number_names[2325] = "septingenquattuorseptuagintillion"
+number_names[2328] = "septingenquinseptuagintillion"
+number_names[2331] = "septingensexseptuagintillion"
+number_names[2334] = "septingenseptenseptuagintillion"
+number_names[2337] = "septingenoctoseptuagintillion"
+number_names[2340] = "septingennovemseptuagintillion"
+number_names[2343] = "septingenoctogintillion"
+number_names[2346] = "septingenunoctogintillion"
+number_names[2349] = "septingendooctogintillion"
+number_names[2352] = "septingentreoctogintillion"
+number_names[2355] = "septingenquattuoroctogintillion"
+number_names[2358] = "septingenquinoctogintillion"
+number_names[2361] = "septingensexoctogintillion"
+number_names[2364] = "septingenseptenoctogintillion"
+number_names[2367] = "septingenoctooctogintillion"
+number_names[2370] = "septingennovemoctogintillion"
+number_names[2373] = "septingennonagintillion"
+number_names[2376] = "septingenunnonagintillion"
+number_names[2379] = "septingendononagintillion"
+number_names[2382] = "septingentrenonagintillion"
+number_names[2385] = "septingenquattuornonagintillion"
+number_names[2388] = "septingenquinnonagintillion"
+number_names[2391] = "septingensexnonagintillion"
+number_names[2394] = "septingenseptennonagintillion"
+number_names[2397] = "septingenoctononagintillion"
+number_names[2400] = "septingennovemnonagintillion"
+number_names[2403] = "octingentillion"
+number_names[2406] = "octingenuntillion"
+number_names[2409] = "octingendotillion"
+number_names[2412] = "octingentretillion"
+number_names[2415] = "octingenquattuortillion"
+number_names[2418] = "octingenquintillion"
+number_names[2421] = "octingensextillion"
+number_names[2424] = "octingenseptentillion"
+number_names[2427] = "octingenoctotillion"
+number_names[2430] = "octingennovemtillion"
+number_names[2433] = "octingendecillion"
+number_names[2436] = "octingenundecillion"
+number_names[2439] = "octingendodecillion"
+number_names[2442] = "octingentredecillion"
+number_names[2445] = "octingenquattuordecillion"
+number_names[2448] = "octingenquindecillion"
+number_names[2451] = "octingensexdecillion"
+number_names[2454] = "octingenseptendecillion"
+number_names[2457] = "octingenoctodecillion"
+number_names[2460] = "octingennovemdecillion"
+number_names[2463] = "octingenvigintillion"
+number_names[2466] = "octingenunvigintillion"
+number_names[2469] = "octingendovigintillion"
+number_names[2472] = "octingentrevigintillion"
+number_names[2475] = "octingenquattuorvigintillion"
+number_names[2478] = "octingenquinvigintillion"
+number_names[2481] = "octingensexvigintillion"
+number_names[2484] = "octingenseptenvigintillion"
+number_names[2487] = "octingenoctovigintillion"
+number_names[2490] = "octingennovemvigintillion"
+number_names[2493] = "octingentrigintillion"
+number_names[2496] = "octingenuntrigintillion"
+number_names[2499] = "octingendotrigintillion"
+number_names[2502] = "octingentretrigintillion"
+number_names[2505] = "octingenquattuortrigintillion"
+number_names[2508] = "octingenquintrigintillion"
+number_names[2511] = "octingensextrigintillion"
+number_names[2514] = "octingenseptentrigintillion"
+number_names[2517] = "octingenoctotrigintillion"
+number_names[2520] = "octingennovemtrigintillion"
+number_names[2523] = "octingenquadragintillion"
+number_names[2526] = "octingenunquadragintillion"
+number_names[2529] = "octingendoquadragintillion"
+number_names[2532] = "octingentrequadragintillion"
+number_names[2535] = "octingenquattuorquadragintillion"
+number_names[2538] = "octingenquinquadragintillion"
+number_names[2541] = "octingensexquadragintillion"
+number_names[2544] = "octingenseptenquadragintillion"
+number_names[2547] = "octingenoctoquadragintillion"
+number_names[2550] = "octingennovemquadragintillion"
+number_names[2553] = "octingenquinquagintillion"
+number_names[2556] = "octingenunquinquagintillion"
+number_names[2559] = "octingendoquinquagintillion"
+number_names[2562] = "octingentrequinquagintillion"
+number_names[2565] = "octingenquattuorquinquagintillion"
+number_names[2568] = "octingenquinquinquagintillion"
+number_names[2571] = "octingensexquinquagintillion"
+number_names[2574] = "octingenseptenquinquagintillion"
+number_names[2577] = "octingenoctoquinquagintillion"
+number_names[2580] = "octingennovemquinquagintillion"
+number_names[2583] = "octingensexagintillion"
+number_names[2586] = "octingenunsexagintillion"
+number_names[2589] = "octingendosexagintillion"
+number_names[2592] = "octingentresexagintillion"
+number_names[2595] = "octingenquattuorsexagintillion"
+number_names[2598] = "octingenquinsexagintillion"
+number_names[2601] = "octingensexsexagintillion"
+number_names[2604] = "octingenseptensexagintillion"
+number_names[2607] = "octingenoctosexagintillion"
+number_names[2610] = "octingennovemsexagintillion"
+number_names[2613] = "octingenseptuagintillion"
+number_names[2616] = "octingenunseptuagintillion"
+number_names[2619] = "octingendoseptuagintillion"
+number_names[2622] = "octingentreseptuagintillion"
+number_names[2625] = "octingenquattuorseptuagintillion"
+number_names[2628] = "octingenquinseptuagintillion"
+number_names[2631] = "octingensexseptuagintillion"
+number_names[2634] = "octingenseptenseptuagintillion"
+number_names[2637] = "octingenoctoseptuagintillion"
+number_names[2640] = "octingennovemseptuagintillion"
+number_names[2643] = "octingenoctogintillion"
+number_names[2646] = "octingenunoctogintillion"
+number_names[2649] = "octingendooctogintillion"
+number_names[2652] = "octingentreoctogintillion"
+number_names[2655] = "octingenquattuoroctogintillion"
+number_names[2658] = "octingenquinoctogintillion"
+number_names[2661] = "octingensexoctogintillion"
+number_names[2664] = "octingenseptenoctogintillion"
+number_names[2667] = "octingenoctooctogintillion"
+number_names[2670] = "octingennovemoctogintillion"
+number_names[2673] = "octingennonagintillion"
+number_names[2676] = "octingenunnonagintillion"
+number_names[2679] = "octingendononagintillion"
+number_names[2682] = "octingentrenonagintillion"
+number_names[2685] = "octingenquattuornonagintillion"
+number_names[2688] = "octingenquinnonagintillion"
+number_names[2691] = "octingensexnonagintillion"
+number_names[2694] = "octingenseptennonagintillion"
+number_names[2697] = "octingenoctononagintillion"
+number_names[2700] = "octingennovemnonagintillion"
+number_names[2703] = "nongentillion"
+number_names[2706] = "nongenuntillion"
+number_names[2709] = "nongendotillion"
+number_names[2712] = "nongentretillion"
+number_names[2715] = "nongenquattuortillion"
+number_names[2718] = "nongenquintillion"
+number_names[2721] = "nongensextillion"
+number_names[2724] = "nongenseptentillion"
+number_names[2727] = "nongenoctotillion"
+number_names[2730] = "nongennovemtillion"
+number_names[2733] = "nongendecillion"
+number_names[2736] = "nongenundecillion"
+number_names[2739] = "nongendodecillion"
+number_names[2742] = "nongentredecillion"
+number_names[2745] = "nongenquattuordecillion"
+number_names[2748] = "nongenquindecillion"
+number_names[2751] = "nongensexdecillion"
+number_names[2754] = "nongenseptendecillion"
+number_names[2757] = "nongenoctodecillion"
+number_names[2760] = "nongennovemdecillion"
+number_names[2763] = "nongenvigintillion"
+number_names[2766] = "nongenunvigintillion"
+number_names[2769] = "nongendovigintillion"
+number_names[2772] = "nongentrevigintillion"
+number_names[2775] = "nongenquattuorvigintillion"
+number_names[2778] = "nongenquinvigintillion"
+number_names[2781] = "nongensexvigintillion"
+number_names[2784] = "nongenseptenvigintillion"
+number_names[2787] = "nongenoctovigintillion"
+number_names[2790] = "nongennovemvigintillion"
+number_names[2793] = "nongentrigintillion"
+number_names[2796] = "nongenuntrigintillion"
+number_names[2799] = "nongendotrigintillion"
+number_names[2802] = "nongentretrigintillion"
+number_names[2805] = "nongenquattuortrigintillion"
+number_names[2808] = "nongenquintrigintillion"
+number_names[2811] = "nongensextrigintillion"
+number_names[2814] = "nongenseptentrigintillion"
+number_names[2817] = "nongenoctotrigintillion"
+number_names[2820] = "nongennovemtrigintillion"
+number_names[2823] = "nongenquadragintillion"
+number_names[2826] = "nongenunquadragintillion"
+number_names[2829] = "nongendoquadragintillion"
+number_names[2832] = "nongentrequadragintillion"
+number_names[2835] = "nongenquattuorquadragintillion"
+number_names[2838] = "nongenquinquadragintillion"
+number_names[2841] = "nongensexquadragintillion"
+number_names[2844] = "nongenseptenquadragintillion"
+number_names[2847] = "nongenoctoquadragintillion"
+number_names[2850] = "nongennovemquadragintillion"
+number_names[2853] = "nongenquinquagintillion"
+number_names[2856] = "nongenunquinquagintillion"
+number_names[2859] = "nongendoquinquagintillion"
+number_names[2862] = "nongentrequinquagintillion"
+number_names[2865] = "nongenquattuorquinquagintillion"
+number_names[2868] = "nongenquinquinquagintillion"
+number_names[2871] = "nongensexquinquagintillion"
+number_names[2874] = "nongenseptenquinquagintillion"
+number_names[2877] = "nongenoctoquinquagintillion"
+number_names[2880] = "nongennovemquinquagintillion"
+number_names[2883] = "nongensexagintillion"
+number_names[2886] = "nongenunsexagintillion"
+number_names[2889] = "nongendosexagintillion"
+number_names[2892] = "nongentresexagintillion"
+number_names[2895] = "nongenquattuorsexagintillion"
+number_names[2898] = "nongenquinsexagintillion"
+number_names[2901] = "nongensexsexagintillion"
+number_names[2904] = "nongenseptensexagintillion"
+number_names[2907] = "nongenoctosexagintillion"
+number_names[2910] = "nongennovemsexagintillion"
+number_names[2913] = "nongenseptuagintillion"
+number_names[2916] = "nongenunseptuagintillion"
+number_names[2919] = "nongendoseptuagintillion"
+number_names[2922] = "nongentreseptuagintillion"
+number_names[2925] = "nongenquattuorseptuagintillion"
+number_names[2928] = "nongenquinseptuagintillion"
+number_names[2931] = "nongensexseptuagintillion"
+number_names[2934] = "nongenseptenseptuagintillion"
+number_names[2937] = "nongenoctoseptuagintillion"
+number_names[2940] = "nongennovemseptuagintillion"
+number_names[2943] = "nongenoctogintillion"
+number_names[2946] = "nongenunoctogintillion"
+number_names[2949] = "nongendooctogintillion"
+number_names[2952] = "nongentreoctogintillion"
+number_names[2955] = "nongenquattuoroctogintillion"
+number_names[2958] = "nongenquinoctogintillion"
+number_names[2961] = "nongensexoctogintillion"
+number_names[2964] = "nongenseptenoctogintillion"
+number_names[2967] = "nongenoctooctogintillion"
+number_names[2970] = "nongennovemoctogintillion"
+number_names[2973] = "nongennonagintillion"
+number_names[2976] = "nongenunnonagintillion"
+number_names[2979] = "nongendononagintillion"
+number_names[2982] = "nongentrenonagintillion"
+number_names[2985] = "nongenquattuornonagintillion"
+number_names[2988] = "nongenquinnonagintillion"
+number_names[2991] = "nongensexnonagintillion"
+number_names[2994] = "nongenseptennonagintillion"
+number_names[2997] = "nongenoctononagintillion"
+number_names[3000] = "nongennovemnonagintillion"
+number_names[3003] = "milliatillion"
+number_names[3006] = "milliauntillion"
+number_names[3009] = "milliadotillion"
+number_names[3012] = "milliatretillion"
+number_names[3015] = "milliaquattuortillion"
+number_names[3018] = "milliaquintillion"
+number_names[3021] = "milliasextillion"
+number_names[3024] = "milliaseptentillion"
+number_names[3027] = "milliaoctotillion"
+number_names[3030] = "millianovemtillion"
+number_names[3033] = "milliadecillion"
+number_names[3036] = "milliaundecillion"
+number_names[3039] = "milliadodecillion"
+number_names[3042] = "milliatredecillion"
+number_names[3045] = "milliaquattuordecillion"
+number_names[3048] = "milliaquindecillion"
+number_names[3051] = "milliasexdecillion"
+number_names[3054] = "milliaseptendecillion"
+number_names[3057] = "milliaoctodecillion"
+number_names[3060] = "millianovemdecillion"
+number_names[3063] = "milliavigintillion"
+number_names[3066] = "milliaunvigintillion"
+number_names[3069] = "milliadovigintillion"
+number_names[3072] = "milliatrevigintillion"
+number_names[3075] = "milliaquattuorvigintillion"
+number_names[3078] = "milliaquinvigintillion"
+number_names[3081] = "milliasexvigintillion"
+number_names[3084] = "milliaseptenvigintillion"
+number_names[3087] = "milliaoctovigintillion"
+number_names[3090] = "millianovemvigintillion"
+number_names[3093] = "milliatrigintillion"
+number_names[3096] = "milliauntrigintillion"
+number_names[3099] = "milliadotrigintillion"
+number_names[3102] = "milliatretrigintillion"
+number_names[3105] = "milliaquattuortrigintillion"
+number_names[3108] = "milliaquintrigintillion"
+number_names[3111] = "milliasextrigintillion"
+number_names[3114] = "milliaseptentrigintillion"
+number_names[3117] = "milliaoctotrigintillion"
+number_names[3120] = "millianovemtrigintillion"
+number_names[3123] = "milliaquadragintillion"
+number_names[3126] = "milliaunquadragintillion"
+number_names[3129] = "milliadoquadragintillion"
+number_names[3132] = "milliatrequadragintillion"
+number_names[3135] = "milliaquattuorquadragintillion"
+number_names[3138] = "milliaquinquadragintillion"
+number_names[3141] = "milliasexquadragintillion"
+number_names[3144] = "milliaseptenquadragintillion"
+number_names[3147] = "milliaoctoquadragintillion"
+number_names[3150] = "millianovemquadragintillion"
+number_names[3153] = "milliaquinquagintillion"
+number_names[3156] = "milliaunquinquagintillion"
+number_names[3159] = "milliadoquinquagintillion"
+number_names[3162] = "milliatrequinquagintillion"
+number_names[3165] = "milliaquattuorquinquagintillion"
+number_names[3168] = "milliaquinquinquagintillion"
+number_names[3171] = "milliasexquinquagintillion"
+number_names[3174] = "milliaseptenquinquagintillion"
+number_names[3177] = "milliaoctoquinquagintillion"
+number_names[3180] = "millianovemquinquagintillion"
+number_names[3183] = "milliasexagintillion"
+number_names[3186] = "milliaunsexagintillion"
+number_names[3189] = "milliadosexagintillion"
+number_names[3192] = "milliatresexagintillion"
+number_names[3195] = "milliaquattuorsexagintillion"
+number_names[3198] = "milliaquinsexagintillion"
+number_names[3201] = "milliasexsexagintillion"
+number_names[3204] = "milliaseptensexagintillion"
+number_names[3207] = "milliaoctosexagintillion"
+number_names[3210] = "millianovemsexagintillion"
+number_names[3213] = "milliaseptuagintillion"
+number_names[3216] = "milliaunseptuagintillion"
+number_names[3219] = "milliadoseptuagintillion"
+number_names[3222] = "milliatreseptuagintillion"
+number_names[3225] = "milliaquattuorseptuagintillion"
+number_names[3228] = "milliaquinseptuagintillion"
+number_names[3231] = "milliasexseptuagintillion"
+number_names[3234] = "milliaseptenseptuagintillion"
+number_names[3237] = "milliaoctoseptuagintillion"
+number_names[3240] = "millianovemseptuagintillion"
+number_names[3243] = "milliaoctogintillion"
+number_names[3246] = "milliaunoctogintillion"
+number_names[3249] = "milliadooctogintillion"
+number_names[3252] = "milliatreoctogintillion"
+number_names[3255] = "milliaquattuoroctogintillion"
+number_names[3258] = "milliaquinoctogintillion"
+number_names[3261] = "milliasexoctogintillion"
+number_names[3264] = "milliaseptenoctogintillion"
+number_names[3267] = "milliaoctooctogintillion"
+number_names[3270] = "millianovemoctogintillion"
+number_names[3273] = "millianonagintillion"
+number_names[3276] = "milliaunnonagintillion"
+number_names[3279] = "milliadononagintillion"
+number_names[3282] = "milliatrenonagintillion"
+number_names[3285] = "milliaquattuornonagintillion"
+number_names[3288] = "milliaquinnonagintillion"
+number_names[3291] = "milliasexnonagintillion"
+number_names[3294] = "milliaseptennonagintillion"
+number_names[3297] = "milliaoctononagintillion"
+number_names[3300] = "millianovemnonagintillion"
+number_names[3303] = "milliacentillion"
+number_names[3306] = "milliacenuntillion"
+number_names[3309] = "milliacendotillion"
+number_names[3312] = "milliacentretillion"
+number_names[3315] = "milliacenquattuortillion"
+number_names[3318] = "milliacenquintillion"
+number_names[3321] = "milliacensextillion"
+number_names[3324] = "milliacenseptentillion"
+number_names[3327] = "milliacenoctotillion"
+number_names[3330] = "milliacennovemtillion"
+number_names[3333] = "milliacendecillion"
+number_names[3336] = "milliacenundecillion"
+number_names[3339] = "milliacendodecillion"
+number_names[3342] = "milliacentredecillion"
+number_names[3345] = "milliacenquattuordecillion"
+number_names[3348] = "milliacenquindecillion"
+number_names[3351] = "milliacensexdecillion"
+number_names[3354] = "milliacenseptendecillion"
+number_names[3357] = "milliacenoctodecillion"
+number_names[3360] = "milliacennovemdecillion"
+number_names[3363] = "milliacenvigintillion"
+number_names[3366] = "milliacenunvigintillion"
+number_names[3369] = "milliacendovigintillion"
+number_names[3372] = "milliacentrevigintillion"
+number_names[3375] = "milliacenquattuorvigintillion"
+number_names[3378] = "milliacenquinvigintillion"
+number_names[3381] = "milliacensexvigintillion"
+number_names[3384] = "milliacenseptenvigintillion"
+number_names[3387] = "milliacenoctovigintillion"
+number_names[3390] = "milliacennovemvigintillion"
+number_names[3393] = "milliacentrigintillion"
+number_names[3396] = "milliacenuntrigintillion"
+number_names[3399] = "milliacendotrigintillion"
+number_names[3402] = "milliacentretrigintillion"
+number_names[3405] = "milliacenquattuortrigintillion"
+number_names[3408] = "milliacenquintrigintillion"
+number_names[3411] = "milliacensextrigintillion"
+number_names[3414] = "milliacenseptentrigintillion"
+number_names[3417] = "milliacenoctotrigintillion"
+number_names[3420] = "milliacennovemtrigintillion"
+number_names[3423] = "milliacenquadragintillion"
+number_names[3426] = "milliacenunquadragintillion"
+number_names[3429] = "milliacendoquadragintillion"
+number_names[3432] = "milliacentrequadragintillion"
+number_names[3435] = "milliacenquattuorquadragintillion"
+number_names[3438] = "milliacenquinquadragintillion"
+number_names[3441] = "milliacensexquadragintillion"
+number_names[3444] = "milliacenseptenquadragintillion"
+number_names[3447] = "milliacenoctoquadragintillion"
+number_names[3450] = "milliacennovemquadragintillion"
+number_names[3453] = "milliacenquinquagintillion"
+number_names[3456] = "milliacenunquinquagintillion"
+number_names[3459] = "milliacendoquinquagintillion"
+number_names[3462] = "milliacentrequinquagintillion"
+number_names[3465] = "milliacenquattuorquinquagintillion"
+number_names[3468] = "milliacenquinquinquagintillion"
+number_names[3471] = "milliacensexquinquagintillion"
+number_names[3474] = "milliacenseptenquinquagintillion"
+number_names[3477] = "milliacenoctoquinquagintillion"
+number_names[3480] = "milliacennovemquinquagintillion"
+number_names[3483] = "milliacensexagintillion"
+number_names[3486] = "milliacenunsexagintillion"
+number_names[3489] = "milliacendosexagintillion"
+number_names[3492] = "milliacentresexagintillion"
+number_names[3495] = "milliacenquattuorsexagintillion"
+number_names[3498] = "milliacenquinsexagintillion"
+number_names[3501] = "milliacensexsexagintillion"
+number_names[3504] = "milliacenseptensexagintillion"
+number_names[3507] = "milliacenoctosexagintillion"
+number_names[3510] = "milliacennovemsexagintillion"
+number_names[3513] = "milliacenseptuagintillion"
+number_names[3516] = "milliacenunseptuagintillion"
+number_names[3519] = "milliacendoseptuagintillion"
+number_names[3522] = "milliacentreseptuagintillion"
+number_names[3525] = "milliacenquattuorseptuagintillion"
+number_names[3528] = "milliacenquinseptuagintillion"
+number_names[3531] = "milliacensexseptuagintillion"
+number_names[3534] = "milliacenseptenseptuagintillion"
+number_names[3537] = "milliacenoctoseptuagintillion"
+number_names[3540] = "milliacennovemseptuagintillion"
+number_names[3543] = "milliacenoctogintillion"
+number_names[3546] = "milliacenunoctogintillion"
+number_names[3549] = "milliacendooctogintillion"
+number_names[3552] = "milliacentreoctogintillion"
+number_names[3555] = "milliacenquattuoroctogintillion"
+number_names[3558] = "milliacenquinoctogintillion"
+number_names[3561] = "milliacensexoctogintillion"
+number_names[3564] = "milliacenseptenoctogintillion"
+number_names[3567] = "milliacenoctooctogintillion"
+number_names[3570] = "milliacennovemoctogintillion"
+number_names[3573] = "milliacennonagintillion"
+number_names[3576] = "milliacenunnonagintillion"
+number_names[3579] = "milliacendononagintillion"
+number_names[3582] = "milliacentrenonagintillion"
+number_names[3585] = "milliacenquattuornonagintillion"
+number_names[3588] = "milliacenquinnonagintillion"
+number_names[3591] = "milliacensexnonagintillion"
+number_names[3594] = "milliacenseptennonagintillion"
+number_names[3597] = "milliacenoctononagintillion"
+number_names[3600] = "milliacennovemnonagintillion"
+number_names[3603] = "milliaducentillion"
+number_names[3606] = "milliaducenuntillion"
+number_names[3609] = "milliaducendotillion"
+number_names[3612] = "milliaducentretillion"
+number_names[3615] = "milliaducenquattuortillion"
+number_names[3618] = "milliaducenquintillion"
+number_names[3621] = "milliaducensextillion"
+number_names[3624] = "milliaducenseptentillion"
+number_names[3627] = "milliaducenoctotillion"
+number_names[3630] = "milliaducennovemtillion"
+number_names[3633] = "milliaducendecillion"
+number_names[3636] = "milliaducenundecillion"
+number_names[3639] = "milliaducendodecillion"
+number_names[3642] = "milliaducentredecillion"
+number_names[3645] = "milliaducenquattuordecillion"
+number_names[3648] = "milliaducenquindecillion"
+number_names[3651] = "milliaducensexdecillion"
+number_names[3654] = "milliaducenseptendecillion"
+number_names[3657] = "milliaducenoctodecillion"
+number_names[3660] = "milliaducennovemdecillion"
+number_names[3663] = "milliaducenvigintillion"
+number_names[3666] = "milliaducenunvigintillion"
+number_names[3669] = "milliaducendovigintillion"
+number_names[3672] = "milliaducentrevigintillion"
+number_names[3675] = "milliaducenquattuorvigintillion"
+number_names[3678] = "milliaducenquinvigintillion"
+number_names[3681] = "milliaducensexvigintillion"
+number_names[3684] = "milliaducenseptenvigintillion"
+number_names[3687] = "milliaducenoctovigintillion"
+number_names[3690] = "milliaducennovemvigintillion"
+number_names[3693] = "milliaducentrigintillion"
+number_names[3696] = "milliaducenuntrigintillion"
+number_names[3699] = "milliaducendotrigintillion"
+number_names[3702] = "milliaducentretrigintillion"
+number_names[3705] = "milliaducenquattuortrigintillion"
+number_names[3708] = "milliaducenquintrigintillion"
+number_names[3711] = "milliaducensextrigintillion"
+number_names[3714] = "milliaducenseptentrigintillion"
+number_names[3717] = "milliaducenoctotrigintillion"
+number_names[3720] = "milliaducennovemtrigintillion"
+number_names[3723] = "milliaducenquadragintillion"
+number_names[3726] = "milliaducenunquadragintillion"
+number_names[3729] = "milliaducendoquadragintillion"
+number_names[3732] = "milliaducentrequadragintillion"
+number_names[3735] = "milliaducenquattuorquadragintillion"
+number_names[3738] = "milliaducenquinquadragintillion"
+number_names[3741] = "milliaducensexquadragintillion"
+number_names[3744] = "milliaducenseptenquadragintillion"
+number_names[3747] = "milliaducenoctoquadragintillion"
+number_names[3750] = "milliaducennovemquadragintillion"
+number_names[3753] = "milliaducenquinquagintillion"
+number_names[3756] = "milliaducenunquinquagintillion"
+number_names[3759] = "milliaducendoquinquagintillion"
+number_names[3762] = "milliaducentrequinquagintillion"
+number_names[3765] = "milliaducenquattuorquinquagintillion"
+number_names[3768] = "milliaducenquinquinquagintillion"
+number_names[3771] = "milliaducensexquinquagintillion"
+number_names[3774] = "milliaducenseptenquinquagintillion"
+number_names[3777] = "milliaducenoctoquinquagintillion"
+number_names[3780] = "milliaducennovemquinquagintillion"
+number_names[3783] = "milliaducensexagintillion"
+number_names[3786] = "milliaducenunsexagintillion"
+number_names[3789] = "milliaducendosexagintillion"
+number_names[3792] = "milliaducentresexagintillion"
+number_names[3795] = "milliaducenquattuorsexagintillion"
+number_names[3798] = "milliaducenquinsexagintillion"
+number_names[3801] = "milliaducensexsexagintillion"
+number_names[3804] = "milliaducenseptensexagintillion"
+number_names[3807] = "milliaducenoctosexagintillion"
+number_names[3810] = "milliaducennovemsexagintillion"
+number_names[3813] = "milliaducenseptuagintillion"
+number_names[3816] = "milliaducenunseptuagintillion"
+number_names[3819] = "milliaducendoseptuagintillion"
+number_names[3822] = "milliaducentreseptuagintillion"
+number_names[3825] = "milliaducenquattuorseptuagintillion"
+number_names[3828] = "milliaducenquinseptuagintillion"
+number_names[3831] = "milliaducensexseptuagintillion"
+number_names[3834] = "milliaducenseptenseptuagintillion"
+number_names[3837] = "milliaducenoctoseptuagintillion"
+number_names[3840] = "milliaducennovemseptuagintillion"
+number_names[3843] = "milliaducenoctogintillion"
+number_names[3846] = "milliaducenunoctogintillion"
+number_names[3849] = "milliaducendooctogintillion"
+number_names[3852] = "milliaducentreoctogintillion"
+number_names[3855] = "milliaducenquattuoroctogintillion"
+number_names[3858] = "milliaducenquinoctogintillion"
+number_names[3861] = "milliaducensexoctogintillion"
+number_names[3864] = "milliaducenseptenoctogintillion"
+number_names[3867] = "milliaducenoctooctogintillion"
+number_names[3870] = "milliaducennovemoctogintillion"
+number_names[3873] = "milliaducennonagintillion"
+number_names[3876] = "milliaducenunnonagintillion"
+number_names[3879] = "milliaducendononagintillion"
+number_names[3882] = "milliaducentrenonagintillion"
+number_names[3885] = "milliaducenquattuornonagintillion"
+number_names[3888] = "milliaducenquinnonagintillion"
+number_names[3891] = "milliaducensexnonagintillion"
+number_names[3894] = "milliaducenseptennonagintillion"
+number_names[3897] = "milliaducenoctononagintillion"
+number_names[3900] = "milliaducennovemnonagintillion"
+number_names[3903] = "milliatrecentillion"
+number_names[3906] = "milliatrecenuntillion"
+number_names[3909] = "milliatrecendotillion"
+number_names[3912] = "milliatrecentretillion"
+number_names[3915] = "milliatrecenquattuortillion"
+number_names[3918] = "milliatrecenquintillion"
+number_names[3921] = "milliatrecensextillion"
+number_names[3924] = "milliatrecenseptentillion"
+number_names[3927] = "milliatrecenoctotillion"
+number_names[3930] = "milliatrecennovemtillion"
+number_names[3933] = "milliatrecendecillion"
+number_names[3936] = "milliatrecenundecillion"
+number_names[3939] = "milliatrecendodecillion"
+number_names[3942] = "milliatrecentredecillion"
+number_names[3945] = "milliatrecenquattuordecillion"
+number_names[3948] = "milliatrecenquindecillion"
+number_names[3951] = "milliatrecensexdecillion"
+number_names[3954] = "milliatrecenseptendecillion"
+number_names[3957] = "milliatrecenoctodecillion"
+number_names[3960] = "milliatrecennovemdecillion"
+number_names[3963] = "milliatrecenvigintillion"
+number_names[3966] = "milliatrecenunvigintillion"
+number_names[3969] = "milliatrecendovigintillion"
+number_names[3972] = "milliatrecentrevigintillion"
+number_names[3975] = "milliatrecenquattuorvigintillion"
+number_names[3978] = "milliatrecenquinvigintillion"
+number_names[3981] = "milliatrecensexvigintillion"
+number_names[3984] = "milliatrecenseptenvigintillion"
+number_names[3987] = "milliatrecenoctovigintillion"
+number_names[3990] = "milliatrecennovemvigintillion"
+number_names[3993] = "milliatrecentrigintillion"
+number_names[3996] = "milliatrecenuntrigintillion"
+number_names[3999] = "milliatrecendotrigintillion"
+number_names[4002] = "milliatrecentretrigintillion"
+number_names[4005] = "milliatrecenquattuortrigintillion"
+number_names[4008] = "milliatrecenquintrigintillion"
+number_names[4011] = "milliatrecensextrigintillion"
+number_names[4014] = "milliatrecenseptentrigintillion"
+number_names[4017] = "milliatrecenoctotrigintillion"
+number_names[4020] = "milliatrecennovemtrigintillion"
+number_names[4023] = "milliatrecenquadragintillion"
+number_names[4026] = "milliatrecenunquadragintillion"
+number_names[4029] = "milliatrecendoquadragintillion"
+number_names[4032] = "milliatrecentrequadragintillion"
+number_names[4035] = "milliatrecenquattuorquadragintillion"
+number_names[4038] = "milliatrecenquinquadragintillion"
+number_names[4041] = "milliatrecensexquadragintillion"
+number_names[4044] = "milliatrecenseptenquadragintillion"
+number_names[4047] = "milliatrecenoctoquadragintillion"
+number_names[4050] = "milliatrecennovemquadragintillion"
+number_names[4053] = "milliatrecenquinquagintillion"
+number_names[4056] = "milliatrecenunquinquagintillion"
+number_names[4059] = "milliatrecendoquinquagintillion"
+number_names[4062] = "milliatrecentrequinquagintillion"
+number_names[4065] = "milliatrecenquattuorquinquagintillion"
+number_names[4068] = "milliatrecenquinquinquagintillion"
+number_names[4071] = "milliatrecensexquinquagintillion"
+number_names[4074] = "milliatrecenseptenquinquagintillion"
+number_names[4077] = "milliatrecenoctoquinquagintillion"
+number_names[4080] = "milliatrecennovemquinquagintillion"
+number_names[4083] = "milliatrecensexagintillion"
+number_names[4086] = "milliatrecenunsexagintillion"
+number_names[4089] = "milliatrecendosexagintillion"
+number_names[4092] = "milliatrecentresexagintillion"
+number_names[4095] = "milliatrecenquattuorsexagintillion"
+number_names[4098] = "milliatrecenquinsexagintillion"
+number_names[4101] = "milliatrecensexsexagintillion"
+number_names[4104] = "milliatrecenseptensexagintillion"
+number_names[4107] = "milliatrecenoctosexagintillion"
+number_names[4110] = "milliatrecennovemsexagintillion"
+number_names[4113] = "milliatrecenseptuagintillion"
+number_names[4116] = "milliatrecenunseptuagintillion"
+number_names[4119] = "milliatrecendoseptuagintillion"
+number_names[4122] = "milliatrecentreseptuagintillion"
+number_names[4125] = "milliatrecenquattuorseptuagintillion"
+number_names[4128] = "milliatrecenquinseptuagintillion"
+number_names[4131] = "milliatrecensexseptuagintillion"
+number_names[4134] = "milliatrecenseptenseptuagintillion"
+number_names[4137] = "milliatrecenoctoseptuagintillion"
+number_names[4140] = "milliatrecennovemseptuagintillion"
+number_names[4143] = "milliatrecenoctogintillion"
+number_names[4146] = "milliatrecenunoctogintillion"
+number_names[4149] = "milliatrecendooctogintillion"
+number_names[4152] = "milliatrecentreoctogintillion"
+number_names[4155] = "milliatrecenquattuoroctogintillion"
+number_names[4158] = "milliatrecenquinoctogintillion"
+number_names[4161] = "milliatrecensexoctogintillion"
+number_names[4164] = "milliatrecenseptenoctogintillion"
+number_names[4167] = "milliatrecenoctooctogintillion"
+number_names[4170] = "milliatrecennovemoctogintillion"
+number_names[4173] = "milliatrecennonagintillion"
+number_names[4176] = "milliatrecenunnonagintillion"
+number_names[4179] = "milliatrecendononagintillion"
+number_names[4182] = "milliatrecentrenonagintillion"
+number_names[4185] = "milliatrecenquattuornonagintillion"
+number_names[4188] = "milliatrecenquinnonagintillion"
+number_names[4191] = "milliatrecensexnonagintillion"
+number_names[4194] = "milliatrecenseptennonagintillion"
+number_names[4197] = "milliatrecenoctononagintillion"
+number_names[4200] = "milliatrecennovemnonagintillion"
+number_names[4203] = "milliaquadringentillion"
+number_names[4206] = "milliaquadringenuntillion"
+number_names[4209] = "milliaquadringendotillion"
+number_names[4212] = "milliaquadringentretillion"
+number_names[4215] = "milliaquadringenquattuortillion"
+number_names[4218] = "milliaquadringenquintillion"
+number_names[4221] = "milliaquadringensextillion"
+number_names[4224] = "milliaquadringenseptentillion"
+number_names[4227] = "milliaquadringenoctotillion"
+number_names[4230] = "milliaquadringennovemtillion"
+number_names[4233] = "milliaquadringendecillion"
+number_names[4236] = "milliaquadringenundecillion"
+number_names[4239] = "milliaquadringendodecillion"
+number_names[4242] = "milliaquadringentredecillion"
+number_names[4245] = "milliaquadringenquattuordecillion"
+number_names[4248] = "milliaquadringenquindecillion"
+number_names[4251] = "milliaquadringensexdecillion"
+number_names[4254] = "milliaquadringenseptendecillion"
+number_names[4257] = "milliaquadringenoctodecillion"
+number_names[4260] = "milliaquadringennovemdecillion"
+number_names[4263] = "milliaquadringenvigintillion"
+number_names[4266] = "milliaquadringenunvigintillion"
+number_names[4269] = "milliaquadringendovigintillion"
+number_names[4272] = "milliaquadringentrevigintillion"
+number_names[4275] = "milliaquadringenquattuorvigintillion"
+number_names[4278] = "milliaquadringenquinvigintillion"
+number_names[4281] = "milliaquadringensexvigintillion"
+number_names[4284] = "milliaquadringenseptenvigintillion"
+number_names[4287] = "milliaquadringenoctovigintillion"
+number_names[4290] = "milliaquadringennovemvigintillion"
+number_names[4293] = "milliaquadringentrigintillion"
+number_names[4296] = "milliaquadringenuntrigintillion"
+number_names[4299] = "milliaquadringendotrigintillion"
+number_names[4302] = "milliaquadringentretrigintillion"
+number_names[4305] = "milliaquadringenquattuortrigintillion"
+number_names[4308] = "milliaquadringenquintrigintillion"
+number_names[4311] = "milliaquadringensextrigintillion"
+number_names[4314] = "milliaquadringenseptentrigintillion"
+number_names[4317] = "milliaquadringenoctotrigintillion"
+number_names[4320] = "milliaquadringennovemtrigintillion"
+number_names[4323] = "milliaquadringenquadragintillion"
+number_names[4326] = "milliaquadringenunquadragintillion"
+number_names[4329] = "milliaquadringendoquadragintillion"
+number_names[4332] = "milliaquadringentrequadragintillion"
+number_names[4335] = "milliaquadringenquattuorquadragintillion"
+number_names[4338] = "milliaquadringenquinquadragintillion"
+number_names[4341] = "milliaquadringensexquadragintillion"
+number_names[4344] = "milliaquadringenseptenquadragintillion"
+number_names[4347] = "milliaquadringenoctoquadragintillion"
+number_names[4350] = "milliaquadringennovemquadragintillion"
+number_names[4353] = "milliaquadringenquinquagintillion"
+number_names[4356] = "milliaquadringenunquinquagintillion"
+number_names[4359] = "milliaquadringendoquinquagintillion"
+number_names[4362] = "milliaquadringentrequinquagintillion"
+number_names[4365] = "milliaquadringenquattuorquinquagintillion"
+number_names[4368] = "milliaquadringenquinquinquagintillion"
+number_names[4371] = "milliaquadringensexquinquagintillion"
+number_names[4374] = "milliaquadringenseptenquinquagintillion"
+number_names[4377] = "milliaquadringenoctoquinquagintillion"
+number_names[4380] = "milliaquadringennovemquinquagintillion"
+number_names[4383] = "milliaquadringensexagintillion"
+number_names[4386] = "milliaquadringenunsexagintillion"
+number_names[4389] = "milliaquadringendosexagintillion"
+number_names[4392] = "milliaquadringentresexagintillion"
+number_names[4395] = "milliaquadringenquattuorsexagintillion"
+number_names[4398] = "milliaquadringenquinsexagintillion"
+number_names[4401] = "milliaquadringensexsexagintillion"
+number_names[4404] = "milliaquadringenseptensexagintillion"
+number_names[4407] = "milliaquadringenoctosexagintillion"
+number_names[4410] = "milliaquadringennovemsexagintillion"
+number_names[4413] = "milliaquadringenseptuagintillion"
+number_names[4416] = "milliaquadringenunseptuagintillion"
+number_names[4419] = "milliaquadringendoseptuagintillion"
+number_names[4422] = "milliaquadringentreseptuagintillion"
+number_names[4425] = "milliaquadringenquattuorseptuagintillion"
+number_names[4428] = "milliaquadringenquinseptuagintillion"
+number_names[4431] = "milliaquadringensexseptuagintillion"
+number_names[4434] = "milliaquadringenseptenseptuagintillion"
+number_names[4437] = "milliaquadringenoctoseptuagintillion"
+number_names[4440] = "milliaquadringennovemseptuagintillion"
+number_names[4443] = "milliaquadringenoctogintillion"
+number_names[4446] = "milliaquadringenunoctogintillion"
+number_names[4449] = "milliaquadringendooctogintillion"
+number_names[4452] = "milliaquadringentreoctogintillion"
+number_names[4455] = "milliaquadringenquattuoroctogintillion"
+number_names[4458] = "milliaquadringenquinoctogintillion"
+number_names[4461] = "milliaquadringensexoctogintillion"
+number_names[4464] = "milliaquadringenseptenoctogintillion"
+number_names[4467] = "milliaquadringenoctooctogintillion"
+number_names[4470] = "milliaquadringennovemoctogintillion"
+number_names[4473] = "milliaquadringennonagintillion"
+number_names[4476] = "milliaquadringenunnonagintillion"
+number_names[4479] = "milliaquadringendononagintillion"
+number_names[4482] = "milliaquadringentrenonagintillion"
+number_names[4485] = "milliaquadringenquattuornonagintillion"
+number_names[4488] = "milliaquadringenquinnonagintillion"
+number_names[4491] = "milliaquadringensexnonagintillion"
+number_names[4494] = "milliaquadringenseptennonagintillion"
+number_names[4497] = "milliaquadringenoctononagintillion"
+number_names[4500] = "milliaquadringennovemnonagintillion"
+number_names[4503] = "milliaquingentillion"
+number_names[4506] = "milliaquingenuntillion"
+number_names[4509] = "milliaquingendotillion"
+number_names[4512] = "milliaquingentretillion"
+number_names[4515] = "milliaquingenquattuortillion"
+number_names[4518] = "milliaquingenquintillion"
+number_names[4521] = "milliaquingensextillion"
+number_names[4524] = "milliaquingenseptentillion"
+number_names[4527] = "milliaquingenoctotillion"
+number_names[4530] = "milliaquingennovemtillion"
+number_names[4533] = "milliaquingendecillion"
+number_names[4536] = "milliaquingenundecillion"
+number_names[4539] = "milliaquingendodecillion"
+number_names[4542] = "milliaquingentredecillion"
+number_names[4545] = "milliaquingenquattuordecillion"
+number_names[4548] = "milliaquingenquindecillion"
+number_names[4551] = "milliaquingensexdecillion"
+number_names[4554] = "milliaquingenseptendecillion"
+number_names[4557] = "milliaquingenoctodecillion"
+number_names[4560] = "milliaquingennovemdecillion"
+number_names[4563] = "milliaquingenvigintillion"
+number_names[4566] = "milliaquingenunvigintillion"
+number_names[4569] = "milliaquingendovigintillion"
+number_names[4572] = "milliaquingentrevigintillion"
+number_names[4575] = "milliaquingenquattuorvigintillion"
+number_names[4578] = "milliaquingenquinvigintillion"
+number_names[4581] = "milliaquingensexvigintillion"
+number_names[4584] = "milliaquingenseptenvigintillion"
+number_names[4587] = "milliaquingenoctovigintillion"
+number_names[4590] = "milliaquingennovemvigintillion"
+number_names[4593] = "milliaquingentrigintillion"
+number_names[4596] = "milliaquingenuntrigintillion"
+number_names[4599] = "milliaquingendotrigintillion"
+number_names[4602] = "milliaquingentretrigintillion"
+number_names[4605] = "milliaquingenquattuortrigintillion"
+number_names[4608] = "milliaquingenquintrigintillion"
+number_names[4611] = "milliaquingensextrigintillion"
+number_names[4614] = "milliaquingenseptentrigintillion"
+number_names[4617] = "milliaquingenoctotrigintillion"
+number_names[4620] = "milliaquingennovemtrigintillion"
+number_names[4623] = "milliaquingenquadragintillion"
+number_names[4626] = "milliaquingenunquadragintillion"
+number_names[4629] = "milliaquingendoquadragintillion"
+number_names[4632] = "milliaquingentrequadragintillion"
+number_names[4635] = "milliaquingenquattuorquadragintillion"
+number_names[4638] = "milliaquingenquinquadragintillion"
+number_names[4641] = "milliaquingensexquadragintillion"
+number_names[4644] = "milliaquingenseptenquadragintillion"
+number_names[4647] = "milliaquingenoctoquadragintillion"
+number_names[4650] = "milliaquingennovemquadragintillion"
+number_names[4653] = "milliaquingenquinquagintillion"
+number_names[4656] = "milliaquingenunquinquagintillion"
+number_names[4659] = "milliaquingendoquinquagintillion"
+number_names[4662] = "milliaquingentrequinquagintillion"
+number_names[4665] = "milliaquingenquattuorquinquagintillion"
+number_names[4668] = "milliaquingenquinquinquagintillion"
+number_names[4671] = "milliaquingensexquinquagintillion"
+number_names[4674] = "milliaquingenseptenquinquagintillion"
+number_names[4677] = "milliaquingenoctoquinquagintillion"
+number_names[4680] = "milliaquingennovemquinquagintillion"
+number_names[4683] = "milliaquingensexagintillion"
+number_names[4686] = "milliaquingenunsexagintillion"
+number_names[4689] = "milliaquingendosexagintillion"
+number_names[4692] = "milliaquingentresexagintillion"
+number_names[4695] = "milliaquingenquattuorsexagintillion"
+number_names[4698] = "milliaquingenquinsexagintillion"
+number_names[4701] = "milliaquingensexsexagintillion"
+number_names[4704] = "milliaquingenseptensexagintillion"
+number_names[4707] = "milliaquingenoctosexagintillion"
+number_names[4710] = "milliaquingennovemsexagintillion"
+number_names[4713] = "milliaquingenseptuagintillion"
+number_names[4716] = "milliaquingenunseptuagintillion"
+number_names[4719] = "milliaquingendoseptuagintillion"
+number_names[4722] = "milliaquingentreseptuagintillion"
+number_names[4725] = "milliaquingenquattuorseptuagintillion"
+number_names[4728] = "milliaquingenquinseptuagintillion"
+number_names[4731] = "milliaquingensexseptuagintillion"
+number_names[4734] = "milliaquingenseptenseptuagintillion"
+number_names[4737] = "milliaquingenoctoseptuagintillion"
+number_names[4740] = "milliaquingennovemseptuagintillion"
+number_names[4743] = "milliaquingenoctogintillion"
+number_names[4746] = "milliaquingenunoctogintillion"
+number_names[4749] = "milliaquingendooctogintillion"
+number_names[4752] = "milliaquingentreoctogintillion"
+number_names[4755] = "milliaquingenquattuoroctogintillion"
+number_names[4758] = "milliaquingenquinoctogintillion"
+number_names[4761] = "milliaquingensexoctogintillion"
+number_names[4764] = "milliaquingenseptenoctogintillion"
+number_names[4767] = "milliaquingenoctooctogintillion"
+number_names[4770] = "milliaquingennovemoctogintillion"
+number_names[4773] = "milliaquingennonagintillion"
+number_names[4776] = "milliaquingenunnonagintillion"
+number_names[4779] = "milliaquingendononagintillion"
+number_names[4782] = "milliaquingentrenonagintillion"
+number_names[4785] = "milliaquingenquattuornonagintillion"
+number_names[4788] = "milliaquingenquinnonagintillion"
+number_names[4791] = "milliaquingensexnonagintillion"
+number_names[4794] = "milliaquingenseptennonagintillion"
+number_names[4797] = "milliaquingenoctononagintillion"
+number_names[4800] = "milliaquingennovemnonagintillion"
+number_names[4803] = "milliasescentillion"
+number_names[4806] = "milliasescenuntillion"
+number_names[4809] = "milliasescendotillion"
+number_names[4812] = "milliasescentretillion"
+number_names[4815] = "milliasescenquattuortillion"
+number_names[4818] = "milliasescenquintillion"
+number_names[4821] = "milliasescensextillion"
+number_names[4824] = "milliasescenseptentillion"
+number_names[4827] = "milliasescenoctotillion"
+number_names[4830] = "milliasescennovemtillion"
+number_names[4833] = "milliasescendecillion"
+number_names[4836] = "milliasescenundecillion"
+number_names[4839] = "milliasescendodecillion"
+number_names[4842] = "milliasescentredecillion"
+number_names[4845] = "milliasescenquattuordecillion"
+number_names[4848] = "milliasescenquindecillion"
+number_names[4851] = "milliasescensexdecillion"
+number_names[4854] = "milliasescenseptendecillion"
+number_names[4857] = "milliasescenoctodecillion"
+number_names[4860] = "milliasescennovemdecillion"
+number_names[4863] = "milliasescenvigintillion"
+number_names[4866] = "milliasescenunvigintillion"
+number_names[4869] = "milliasescendovigintillion"
+number_names[4872] = "milliasescentrevigintillion"
+number_names[4875] = "milliasescenquattuorvigintillion"
+number_names[4878] = "milliasescenquinvigintillion"
+number_names[4881] = "milliasescensexvigintillion"
+number_names[4884] = "milliasescenseptenvigintillion"
+number_names[4887] = "milliasescenoctovigintillion"
+number_names[4890] = "milliasescennovemvigintillion"
+number_names[4893] = "milliasescentrigintillion"
+number_names[4896] = "milliasescenuntrigintillion"
+number_names[4899] = "milliasescendotrigintillion"
+number_names[4902] = "milliasescentretrigintillion"
+number_names[4905] = "milliasescenquattuortrigintillion"
+number_names[4908] = "milliasescenquintrigintillion"
+number_names[4911] = "milliasescensextrigintillion"
+number_names[4914] = "milliasescenseptentrigintillion"
+number_names[4917] = "milliasescenoctotrigintillion"
+number_names[4920] = "milliasescennovemtrigintillion"
+number_names[4923] = "milliasescenquadragintillion"
+number_names[4926] = "milliasescenunquadragintillion"
+number_names[4929] = "milliasescendoquadragintillion"
+number_names[4932] = "milliasescentrequadragintillion"
+number_names[4935] = "milliasescenquattuorquadragintillion"
+number_names[4938] = "milliasescenquinquadragintillion"
+number_names[4941] = "milliasescensexquadragintillion"
+number_names[4944] = "milliasescenseptenquadragintillion"
+number_names[4947] = "milliasescenoctoquadragintillion"
+number_names[4950] = "milliasescennovemquadragintillion"
+number_names[4953] = "milliasescenquinquagintillion"
+number_names[4956] = "milliasescenunquinquagintillion"
+number_names[4959] = "milliasescendoquinquagintillion"
+number_names[4962] = "milliasescentrequinquagintillion"
+number_names[4965] = "milliasescenquattuorquinquagintillion"
+number_names[4968] = "milliasescenquinquinquagintillion"
+number_names[4971] = "milliasescensexquinquagintillion"
+number_names[4974] = "milliasescenseptenquinquagintillion"
+number_names[4977] = "milliasescenoctoquinquagintillion"
+number_names[4980] = "milliasescennovemquinquagintillion"
+number_names[4983] = "milliasescensexagintillion"
+number_names[4986] = "milliasescenunsexagintillion"
+number_names[4989] = "milliasescendosexagintillion"
+number_names[4992] = "milliasescentresexagintillion"
+number_names[4995] = "milliasescenquattuorsexagintillion"
+number_names[4998] = "milliasescenquinsexagintillion"
+number_names[5001] = "milliasescensexsexagintillion"
+number_names[5004] = "milliasescenseptensexagintillion"
+number_names[5007] = "milliasescenoctosexagintillion"
+number_names[5010] = "milliasescennovemsexagintillion"
+number_names[5013] = "milliasescenseptuagintillion"
+number_names[5016] = "milliasescenunseptuagintillion"
+number_names[5019] = "milliasescendoseptuagintillion"
+number_names[5022] = "milliasescentreseptuagintillion"
+number_names[5025] = "milliasescenquattuorseptuagintillion"
+number_names[5028] = "milliasescenquinseptuagintillion"
+number_names[5031] = "milliasescensexseptuagintillion"
+number_names[5034] = "milliasescenseptenseptuagintillion"
+number_names[5037] = "milliasescenoctoseptuagintillion"
+number_names[5040] = "milliasescennovemseptuagintillion"
+number_names[5043] = "milliasescenoctogintillion"
+number_names[5046] = "milliasescenunoctogintillion"
+number_names[5049] = "milliasescendooctogintillion"
+number_names[5052] = "milliasescentreoctogintillion"
+number_names[5055] = "milliasescenquattuoroctogintillion"
+number_names[5058] = "milliasescenquinoctogintillion"
+number_names[5061] = "milliasescensexoctogintillion"
+number_names[5064] = "milliasescenseptenoctogintillion"
+number_names[5067] = "milliasescenoctooctogintillion"
+number_names[5070] = "milliasescennovemoctogintillion"
+number_names[5073] = "milliasescennonagintillion"
+number_names[5076] = "milliasescenunnonagintillion"
+number_names[5079] = "milliasescendononagintillion"
+number_names[5082] = "milliasescentrenonagintillion"
+number_names[5085] = "milliasescenquattuornonagintillion"
+number_names[5088] = "milliasescenquinnonagintillion"
+number_names[5091] = "milliasescensexnonagintillion"
+number_names[5094] = "milliasescenseptennonagintillion"
+number_names[5097] = "milliasescenoctononagintillion"
+number_names[5100] = "milliasescennovemnonagintillion"
+number_names[5103] = "milliaseptingentillion"
+number_names[5106] = "milliaseptingenuntillion"
+number_names[5109] = "milliaseptingendotillion"
+number_names[5112] = "milliaseptingentretillion"
+number_names[5115] = "milliaseptingenquattuortillion"
+number_names[5118] = "milliaseptingenquintillion"
+number_names[5121] = "milliaseptingensextillion"
+number_names[5124] = "milliaseptingenseptentillion"
+number_names[5127] = "milliaseptingenoctotillion"
+number_names[5130] = "milliaseptingennovemtillion"
+number_names[5133] = "milliaseptingendecillion"
+number_names[5136] = "milliaseptingenundecillion"
+number_names[5139] = "milliaseptingendodecillion"
+number_names[5142] = "milliaseptingentredecillion"
+number_names[5145] = "milliaseptingenquattuordecillion"
+number_names[5148] = "milliaseptingenquindecillion"
+number_names[5151] = "milliaseptingensexdecillion"
+number_names[5154] = "milliaseptingenseptendecillion"
+number_names[5157] = "milliaseptingenoctodecillion"
+number_names[5160] = "milliaseptingennovemdecillion"
+number_names[5163] = "milliaseptingenvigintillion"
+number_names[5166] = "milliaseptingenunvigintillion"
+number_names[5169] = "milliaseptingendovigintillion"
+number_names[5172] = "milliaseptingentrevigintillion"
+number_names[5175] = "milliaseptingenquattuorvigintillion"
+number_names[5178] = "milliaseptingenquinvigintillion"
+number_names[5181] = "milliaseptingensexvigintillion"
+number_names[5184] = "milliaseptingenseptenvigintillion"
+number_names[5187] = "milliaseptingenoctovigintillion"
+number_names[5190] = "milliaseptingennovemvigintillion"
+number_names[5193] = "milliaseptingentrigintillion"
+number_names[5196] = "milliaseptingenuntrigintillion"
+number_names[5199] = "milliaseptingendotrigintillion"
+number_names[5202] = "milliaseptingentretrigintillion"
+number_names[5205] = "milliaseptingenquattuortrigintillion"
+number_names[5208] = "milliaseptingenquintrigintillion"
+number_names[5211] = "milliaseptingensextrigintillion"
+number_names[5214] = "milliaseptingenseptentrigintillion"
+number_names[5217] = "milliaseptingenoctotrigintillion"
+number_names[5220] = "milliaseptingennovemtrigintillion"
+number_names[5223] = "milliaseptingenquadragintillion"
+number_names[5226] = "milliaseptingenunquadragintillion"
+number_names[5229] = "milliaseptingendoquadragintillion"
+number_names[5232] = "milliaseptingentrequadragintillion"
+number_names[5235] = "milliaseptingenquattuorquadragintillion"
+number_names[5238] = "milliaseptingenquinquadragintillion"
+number_names[5241] = "milliaseptingensexquadragintillion"
+number_names[5244] = "milliaseptingenseptenquadragintillion"
+number_names[5247] = "milliaseptingenoctoquadragintillion"
+number_names[5250] = "milliaseptingennovemquadragintillion"
+number_names[5253] = "milliaseptingenquinquagintillion"
+number_names[5256] = "milliaseptingenunquinquagintillion"
+number_names[5259] = "milliaseptingendoquinquagintillion"
+number_names[5262] = "milliaseptingentrequinquagintillion"
+number_names[5265] = "milliaseptingenquattuorquinquagintillion"
+number_names[5268] = "milliaseptingenquinquinquagintillion"
+number_names[5271] = "milliaseptingensexquinquagintillion"
+number_names[5274] = "milliaseptingenseptenquinquagintillion"
+number_names[5277] = "milliaseptingenoctoquinquagintillion"
+number_names[5280] = "milliaseptingennovemquinquagintillion"
+number_names[5283] = "milliaseptingensexagintillion"
+number_names[5286] = "milliaseptingenunsexagintillion"
+number_names[5289] = "milliaseptingendosexagintillion"
+number_names[5292] = "milliaseptingentresexagintillion"
+number_names[5295] = "milliaseptingenquattuorsexagintillion"
+number_names[5298] = "milliaseptingenquinsexagintillion"
+number_names[5301] = "milliaseptingensexsexagintillion"
+number_names[5304] = "milliaseptingenseptensexagintillion"
+number_names[5307] = "milliaseptingenoctosexagintillion"
+number_names[5310] = "milliaseptingennovemsexagintillion"
+number_names[5313] = "milliaseptingenseptuagintillion"
+number_names[5316] = "milliaseptingenunseptuagintillion"
+number_names[5319] = "milliaseptingendoseptuagintillion"
+number_names[5322] = "milliaseptingentreseptuagintillion"
+number_names[5325] = "milliaseptingenquattuorseptuagintillion"
+number_names[5328] = "milliaseptingenquinseptuagintillion"
+number_names[5331] = "milliaseptingensexseptuagintillion"
+number_names[5334] = "milliaseptingenseptenseptuagintillion"
+number_names[5337] = "milliaseptingenoctoseptuagintillion"
+number_names[5340] = "milliaseptingennovemseptuagintillion"
+number_names[5343] = "milliaseptingenoctogintillion"
+number_names[5346] = "milliaseptingenunoctogintillion"
+number_names[5349] = "milliaseptingendooctogintillion"
+number_names[5352] = "milliaseptingentreoctogintillion"
+number_names[5355] = "milliaseptingenquattuoroctogintillion"
+number_names[5358] = "milliaseptingenquinoctogintillion"
+number_names[5361] = "milliaseptingensexoctogintillion"
+number_names[5364] = "milliaseptingenseptenoctogintillion"
+number_names[5367] = "milliaseptingenoctooctogintillion"
+number_names[5370] = "milliaseptingennovemoctogintillion"
+number_names[5373] = "milliaseptingennonagintillion"
+number_names[5376] = "milliaseptingenunnonagintillion"
+number_names[5379] = "milliaseptingendononagintillion"
+number_names[5382] = "milliaseptingentrenonagintillion"
+number_names[5385] = "milliaseptingenquattuornonagintillion"
+number_names[5388] = "milliaseptingenquinnonagintillion"
+number_names[5391] = "milliaseptingensexnonagintillion"
+number_names[5394] = "milliaseptingenseptennonagintillion"
+number_names[5397] = "milliaseptingenoctononagintillion"
+number_names[5400] = "milliaseptingennovemnonagintillion"
+number_names[5403] = "milliaoctingentillion"
+number_names[5406] = "milliaoctingenuntillion"
+number_names[5409] = "milliaoctingendotillion"
+number_names[5412] = "milliaoctingentretillion"
+number_names[5415] = "milliaoctingenquattuortillion"
+number_names[5418] = "milliaoctingenquintillion"
+number_names[5421] = "milliaoctingensextillion"
+number_names[5424] = "milliaoctingenseptentillion"
+number_names[5427] = "milliaoctingenoctotillion"
+number_names[5430] = "milliaoctingennovemtillion"
+number_names[5433] = "milliaoctingendecillion"
+number_names[5436] = "milliaoctingenundecillion"
+number_names[5439] = "milliaoctingendodecillion"
+number_names[5442] = "milliaoctingentredecillion"
+number_names[5445] = "milliaoctingenquattuordecillion"
+number_names[5448] = "milliaoctingenquindecillion"
+number_names[5451] = "milliaoctingensexdecillion"
+number_names[5454] = "milliaoctingenseptendecillion"
+number_names[5457] = "milliaoctingenoctodecillion"
+number_names[5460] = "milliaoctingennovemdecillion"
+number_names[5463] = "milliaoctingenvigintillion"
+number_names[5466] = "milliaoctingenunvigintillion"
+number_names[5469] = "milliaoctingendovigintillion"
+number_names[5472] = "milliaoctingentrevigintillion"
+number_names[5475] = "milliaoctingenquattuorvigintillion"
+number_names[5478] = "milliaoctingenquinvigintillion"
+number_names[5481] = "milliaoctingensexvigintillion"
+number_names[5484] = "milliaoctingenseptenvigintillion"
+number_names[5487] = "milliaoctingenoctovigintillion"
+number_names[5490] = "milliaoctingennovemvigintillion"
+number_names[5493] = "milliaoctingentrigintillion"
+number_names[5496] = "milliaoctingenuntrigintillion"
+number_names[5499] = "milliaoctingendotrigintillion"
+number_names[5502] = "milliaoctingentretrigintillion"
+number_names[5505] = "milliaoctingenquattuortrigintillion"
+number_names[5508] = "milliaoctingenquintrigintillion"
+number_names[5511] = "milliaoctingensextrigintillion"
+number_names[5514] = "milliaoctingenseptentrigintillion"
+number_names[5517] = "milliaoctingenoctotrigintillion"
+number_names[5520] = "milliaoctingennovemtrigintillion"
+number_names[5523] = "milliaoctingenquadragintillion"
+number_names[5526] = "milliaoctingenunquadragintillion"
+number_names[5529] = "milliaoctingendoquadragintillion"
+number_names[5532] = "milliaoctingentrequadragintillion"
+number_names[5535] = "milliaoctingenquattuorquadragintillion"
+number_names[5538] = "milliaoctingenquinquadragintillion"
+number_names[5541] = "milliaoctingensexquadragintillion"
+number_names[5544] = "milliaoctingenseptenquadragintillion"
+number_names[5547] = "milliaoctingenoctoquadragintillion"
+number_names[5550] = "milliaoctingennovemquadragintillion"
+number_names[5553] = "milliaoctingenquinquagintillion"
+number_names[5556] = "milliaoctingenunquinquagintillion"
+number_names[5559] = "milliaoctingendoquinquagintillion"
+number_names[5562] = "milliaoctingentrequinquagintillion"
+number_names[5565] = "milliaoctingenquattuorquinquagintillion"
+number_names[5568] = "milliaoctingenquinquinquagintillion"
+number_names[5571] = "milliaoctingensexquinquagintillion"
+number_names[5574] = "milliaoctingenseptenquinquagintillion"
+number_names[5577] = "milliaoctingenoctoquinquagintillion"
+number_names[5580] = "milliaoctingennovemquinquagintillion"
+number_names[5583] = "milliaoctingensexagintillion"
+number_names[5586] = "milliaoctingenunsexagintillion"
+number_names[5589] = "milliaoctingendosexagintillion"
+number_names[5592] = "milliaoctingentresexagintillion"
+number_names[5595] = "milliaoctingenquattuorsexagintillion"
+number_names[5598] = "milliaoctingenquinsexagintillion"
+number_names[5601] = "milliaoctingensexsexagintillion"
+number_names[5604] = "milliaoctingenseptensexagintillion"
+number_names[5607] = "milliaoctingenoctosexagintillion"
+number_names[5610] = "milliaoctingennovemsexagintillion"
+number_names[5613] = "milliaoctingenseptuagintillion"
+number_names[5616] = "milliaoctingenunseptuagintillion"
+number_names[5619] = "milliaoctingendoseptuagintillion"
+number_names[5622] = "milliaoctingentreseptuagintillion"
+number_names[5625] = "milliaoctingenquattuorseptuagintillion"
+number_names[5628] = "milliaoctingenquinseptuagintillion"
+number_names[5631] = "milliaoctingensexseptuagintillion"
+number_names[5634] = "milliaoctingenseptenseptuagintillion"
+number_names[5637] = "milliaoctingenoctoseptuagintillion"
+number_names[5640] = "milliaoctingennovemseptuagintillion"
+number_names[5643] = "milliaoctingenoctogintillion"
+number_names[5646] = "milliaoctingenunoctogintillion"
+number_names[5649] = "milliaoctingendooctogintillion"
+number_names[5652] = "milliaoctingentreoctogintillion"
+number_names[5655] = "milliaoctingenquattuoroctogintillion"
+number_names[5658] = "milliaoctingenquinoctogintillion"
+number_names[5661] = "milliaoctingensexoctogintillion"
+number_names[5664] = "milliaoctingenseptenoctogintillion"
+number_names[5667] = "milliaoctingenoctooctogintillion"
+number_names[5670] = "milliaoctingennovemoctogintillion"
+number_names[5673] = "milliaoctingennonagintillion"
+number_names[5676] = "milliaoctingenunnonagintillion"
+number_names[5679] = "milliaoctingendononagintillion"
+number_names[5682] = "milliaoctingentrenonagintillion"
+number_names[5685] = "milliaoctingenquattuornonagintillion"
+number_names[5688] = "milliaoctingenquinnonagintillion"
+number_names[5691] = "milliaoctingensexnonagintillion"
+number_names[5694] = "milliaoctingenseptennonagintillion"
+number_names[5697] = "milliaoctingenoctononagintillion"
+number_names[5700] = "milliaoctingennovemnonagintillion"
+number_names[5703] = "millianongentillion"
+number_names[5706] = "millianongenuntillion"
+number_names[5709] = "millianongendotillion"
+number_names[5712] = "millianongentretillion"
+number_names[5715] = "millianongenquattuortillion"
+number_names[5718] = "millianongenquintillion"
+number_names[5721] = "millianongensextillion"
+number_names[5724] = "millianongenseptentillion"
+number_names[5727] = "millianongenoctotillion"
+number_names[5730] = "millianongennovemtillion"
+number_names[5733] = "millianongendecillion"
+number_names[5736] = "millianongenundecillion"
+number_names[5739] = "millianongendodecillion"
+number_names[5742] = "millianongentredecillion"
+number_names[5745] = "millianongenquattuordecillion"
+number_names[5748] = "millianongenquindecillion"
+number_names[5751] = "millianongensexdecillion"
+number_names[5754] = "millianongenseptendecillion"
+number_names[5757] = "millianongenoctodecillion"
+number_names[5760] = "millianongennovemdecillion"
+number_names[5763] = "millianongenvigintillion"
+number_names[5766] = "millianongenunvigintillion"
+number_names[5769] = "millianongendovigintillion"
+number_names[5772] = "millianongentrevigintillion"
+number_names[5775] = "millianongenquattuorvigintillion"
+number_names[5778] = "millianongenquinvigintillion"
+number_names[5781] = "millianongensexvigintillion"
+number_names[5784] = "millianongenseptenvigintillion"
+number_names[5787] = "millianongenoctovigintillion"
+number_names[5790] = "millianongennovemvigintillion"
+number_names[5793] = "millianongentrigintillion"
+number_names[5796] = "millianongenuntrigintillion"
+number_names[5799] = "millianongendotrigintillion"
+number_names[5802] = "millianongentretrigintillion"
+number_names[5805] = "millianongenquattuortrigintillion"
+number_names[5808] = "millianongenquintrigintillion"
+number_names[5811] = "millianongensextrigintillion"
+number_names[5814] = "millianongenseptentrigintillion"
+number_names[5817] = "millianongenoctotrigintillion"
+number_names[5820] = "millianongennovemtrigintillion"
+number_names[5823] = "millianongenquadragintillion"
+number_names[5826] = "millianongenunquadragintillion"
+number_names[5829] = "millianongendoquadragintillion"
+number_names[5832] = "millianongentrequadragintillion"
+number_names[5835] = "millianongenquattuorquadragintillion"
+number_names[5838] = "millianongenquinquadragintillion"
+number_names[5841] = "millianongensexquadragintillion"
+number_names[5844] = "millianongenseptenquadragintillion"
+number_names[5847] = "millianongenoctoquadragintillion"
+number_names[5850] = "millianongennovemquadragintillion"
+number_names[5853] = "millianongenquinquagintillion"
+number_names[5856] = "millianongenunquinquagintillion"
+number_names[5859] = "millianongendoquinquagintillion"
+number_names[5862] = "millianongentrequinquagintillion"
+number_names[5865] = "millianongenquattuorquinquagintillion"
+number_names[5868] = "millianongenquinquinquagintillion"
+number_names[5871] = "millianongensexquinquagintillion"
+number_names[5874] = "millianongenseptenquinquagintillion"
+number_names[5877] = "millianongenoctoquinquagintillion"
+number_names[5880] = "millianongennovemquinquagintillion"
+number_names[5883] = "millianongensexagintillion"
+number_names[5886] = "millianongenunsexagintillion"
+number_names[5889] = "millianongendosexagintillion"
+number_names[5892] = "millianongentresexagintillion"
+number_names[5895] = "millianongenquattuorsexagintillion"
+number_names[5898] = "millianongenquinsexagintillion"
+number_names[5901] = "millianongensexsexagintillion"
+number_names[5904] = "millianongenseptensexagintillion"
+number_names[5907] = "millianongenoctosexagintillion"
+number_names[5910] = "millianongennovemsexagintillion"
+number_names[5913] = "millianongenseptuagintillion"
+number_names[5916] = "millianongenunseptuagintillion"
+number_names[5919] = "millianongendoseptuagintillion"
+number_names[5922] = "millianongentreseptuagintillion"
+number_names[5925] = "millianongenquattuorseptuagintillion"
+number_names[5928] = "millianongenquinseptuagintillion"
+number_names[5931] = "millianongensexseptuagintillion"
+number_names[5934] = "millianongenseptenseptuagintillion"
+number_names[5937] = "millianongenoctoseptuagintillion"
+number_names[5940] = "millianongennovemseptuagintillion"
+number_names[5943] = "millianongenoctogintillion"
+number_names[5946] = "millianongenunoctogintillion"
+number_names[5949] = "millianongendooctogintillion"
+number_names[5952] = "millianongentreoctogintillion"
+number_names[5955] = "millianongenquattuoroctogintillion"
+number_names[5958] = "millianongenquinoctogintillion"
+number_names[5961] = "millianongensexoctogintillion"
+number_names[5964] = "millianongenseptenoctogintillion"
+number_names[5967] = "millianongenoctooctogintillion"
+number_names[5970] = "millianongennovemoctogintillion"
+number_names[5973] = "millianongennonagintillion"
+number_names[5976] = "millianongenunnonagintillion"
+number_names[5979] = "millianongendononagintillion"
+number_names[5982] = "millianongentrenonagintillion"
+number_names[5985] = "millianongenquattuornonagintillion"
+number_names[5988] = "millianongenquinnonagintillion"
+number_names[5991] = "millianongensexnonagintillion"
+number_names[5994] = "millianongenseptennonagintillion"
+number_names[5997] = "millianongenoctononagintillion"
+number_names[6000] = "millianongennovemnonagintillion"
+number_names[6003] = "duomilliatillion"
+number_names[6006] = "duomilliauntillion"
+number_names[6009] = "duomilliadotillion"
+number_names[6012] = "duomilliatretillion"
+number_names[6015] = "duomilliaquattuortillion"
+number_names[6018] = "duomilliaquintillion"
+number_names[6021] = "duomilliasextillion"
+number_names[6024] = "duomilliaseptentillion"
+number_names[6027] = "duomilliaoctotillion"
+number_names[6030] = "duomillianovemtillion"
+number_names[6033] = "duomilliadecillion"
+number_names[6036] = "duomilliaundecillion"
+number_names[6039] = "duomilliadodecillion"
+number_names[6042] = "duomilliatredecillion"
+number_names[6045] = "duomilliaquattuordecillion"
+number_names[6048] = "duomilliaquindecillion"
+number_names[6051] = "duomilliasexdecillion"
+number_names[6054] = "duomilliaseptendecillion"
+number_names[6057] = "duomilliaoctodecillion"
+number_names[6060] = "duomillianovemdecillion"
+number_names[6063] = "duomilliavigintillion"
+number_names[6066] = "duomilliaunvigintillion"
+number_names[6069] = "duomilliadovigintillion"
+number_names[6072] = "duomilliatrevigintillion"
+number_names[6075] = "duomilliaquattuorvigintillion"
+number_names[6078] = "duomilliaquinvigintillion"
+number_names[6081] = "duomilliasexvigintillion"
+number_names[6084] = "duomilliaseptenvigintillion"
+number_names[6087] = "duomilliaoctovigintillion"
+number_names[6090] = "duomillianovemvigintillion"
+number_names[6093] = "duomilliatrigintillion"
+number_names[6096] = "duomilliauntrigintillion"
+number_names[6099] = "duomilliadotrigintillion"
+number_names[6102] = "duomilliatretrigintillion"
+number_names[6105] = "duomilliaquattuortrigintillion"
+number_names[6108] = "duomilliaquintrigintillion"
+number_names[6111] = "duomilliasextrigintillion"
+number_names[6114] = "duomilliaseptentrigintillion"
+number_names[6117] = "duomilliaoctotrigintillion"
+number_names[6120] = "duomillianovemtrigintillion"
+number_names[6123] = "duomilliaquadragintillion"
+number_names[6126] = "duomilliaunquadragintillion"
+number_names[6129] = "duomilliadoquadragintillion"
+number_names[6132] = "duomilliatrequadragintillion"
+number_names[6135] = "duomilliaquattuorquadragintillion"
+number_names[6138] = "duomilliaquinquadragintillion"
+number_names[6141] = "duomilliasexquadragintillion"
+number_names[6144] = "duomilliaseptenquadragintillion"
+number_names[6147] = "duomilliaoctoquadragintillion"
+number_names[6150] = "duomillianovemquadragintillion"
+number_names[6153] = "duomilliaquinquagintillion"
+number_names[6156] = "duomilliaunquinquagintillion"
+number_names[6159] = "duomilliadoquinquagintillion"
+number_names[6162] = "duomilliatrequinquagintillion"
+number_names[6165] = "duomilliaquattuorquinquagintillion"
+number_names[6168] = "duomilliaquinquinquagintillion"
+number_names[6171] = "duomilliasexquinquagintillion"
+number_names[6174] = "duomilliaseptenquinquagintillion"
+number_names[6177] = "duomilliaoctoquinquagintillion"
+number_names[6180] = "duomillianovemquinquagintillion"
+number_names[6183] = "duomilliasexagintillion"
+number_names[6186] = "duomilliaunsexagintillion"
+number_names[6189] = "duomilliadosexagintillion"
+number_names[6192] = "duomilliatresexagintillion"
+number_names[6195] = "duomilliaquattuorsexagintillion"
+number_names[6198] = "duomilliaquinsexagintillion"
+number_names[6201] = "duomilliasexsexagintillion"
+number_names[6204] = "duomilliaseptensexagintillion"
+number_names[6207] = "duomilliaoctosexagintillion"
+number_names[6210] = "duomillianovemsexagintillion"
+number_names[6213] = "duomilliaseptuagintillion"
+number_names[6216] = "duomilliaunseptuagintillion"
+number_names[6219] = "duomilliadoseptuagintillion"
+number_names[6222] = "duomilliatreseptuagintillion"
+number_names[6225] = "duomilliaquattuorseptuagintillion"
+number_names[6228] = "duomilliaquinseptuagintillion"
+number_names[6231] = "duomilliasexseptuagintillion"
+number_names[6234] = "duomilliaseptenseptuagintillion"
+number_names[6237] = "duomilliaoctoseptuagintillion"
+number_names[6240] = "duomillianovemseptuagintillion"
+number_names[6243] = "duomilliaoctogintillion"
+number_names[6246] = "duomilliaunoctogintillion"
+number_names[6249] = "duomilliadooctogintillion"
+number_names[6252] = "duomilliatreoctogintillion"
+number_names[6255] = "duomilliaquattuoroctogintillion"
+number_names[6258] = "duomilliaquinoctogintillion"
+number_names[6261] = "duomilliasexoctogintillion"
+number_names[6264] = "duomilliaseptenoctogintillion"
+number_names[6267] = "duomilliaoctooctogintillion"
+number_names[6270] = "duomillianovemoctogintillion"
+number_names[6273] = "duomillianonagintillion"
+number_names[6276] = "duomilliaunnonagintillion"
+number_names[6279] = "duomilliadononagintillion"
+number_names[6282] = "duomilliatrenonagintillion"
+number_names[6285] = "duomilliaquattuornonagintillion"
+number_names[6288] = "duomilliaquinnonagintillion"
+number_names[6291] = "duomilliasexnonagintillion"
+number_names[6294] = "duomilliaseptennonagintillion"
+number_names[6297] = "duomilliaoctononagintillion"
+number_names[6300] = "duomillianovemnonagintillion"
+number_names[6303] = "duomilliacentillion"
+number_names[6306] = "duomilliacenuntillion"
+number_names[6309] = "duomilliacendotillion"
+number_names[6312] = "duomilliacentretillion"
+number_names[6315] = "duomilliacenquattuortillion"
+number_names[6318] = "duomilliacenquintillion"
+number_names[6321] = "duomilliacensextillion"
+number_names[6324] = "duomilliacenseptentillion"
+number_names[6327] = "duomilliacenoctotillion"
+number_names[6330] = "duomilliacennovemtillion"
+number_names[6333] = "duomilliacendecillion"
+number_names[6336] = "duomilliacenundecillion"
+number_names[6339] = "duomilliacendodecillion"
+number_names[6342] = "duomilliacentredecillion"
+number_names[6345] = "duomilliacenquattuordecillion"
+number_names[6348] = "duomilliacenquindecillion"
+number_names[6351] = "duomilliacensexdecillion"
+number_names[6354] = "duomilliacenseptendecillion"
+number_names[6357] = "duomilliacenoctodecillion"
+number_names[6360] = "duomilliacennovemdecillion"
+number_names[6363] = "duomilliacenvigintillion"
+number_names[6366] = "duomilliacenunvigintillion"
+number_names[6369] = "duomilliacendovigintillion"
+number_names[6372] = "duomilliacentrevigintillion"
+number_names[6375] = "duomilliacenquattuorvigintillion"
+number_names[6378] = "duomilliacenquinvigintillion"
+number_names[6381] = "duomilliacensexvigintillion"
+number_names[6384] = "duomilliacenseptenvigintillion"
+number_names[6387] = "duomilliacenoctovigintillion"
+number_names[6390] = "duomilliacennovemvigintillion"
+number_names[6393] = "duomilliacentrigintillion"
+number_names[6396] = "duomilliacenuntrigintillion"
+number_names[6399] = "duomilliacendotrigintillion"
+number_names[6402] = "duomilliacentretrigintillion"
+number_names[6405] = "duomilliacenquattuortrigintillion"
+number_names[6408] = "duomilliacenquintrigintillion"
+number_names[6411] = "duomilliacensextrigintillion"
+number_names[6414] = "duomilliacenseptentrigintillion"
+number_names[6417] = "duomilliacenoctotrigintillion"
+number_names[6420] = "duomilliacennovemtrigintillion"
+number_names[6423] = "duomilliacenquadragintillion"
+number_names[6426] = "duomilliacenunquadragintillion"
+number_names[6429] = "duomilliacendoquadragintillion"
+number_names[6432] = "duomilliacentrequadragintillion"
+number_names[6435] = "duomilliacenquattuorquadragintillion"
+number_names[6438] = "duomilliacenquinquadragintillion"
+number_names[6441] = "duomilliacensexquadragintillion"
+number_names[6444] = "duomilliacenseptenquadragintillion"
+number_names[6447] = "duomilliacenoctoquadragintillion"
+number_names[6450] = "duomilliacennovemquadragintillion"
+number_names[6453] = "duomilliacenquinquagintillion"
+number_names[6456] = "duomilliacenunquinquagintillion"
+number_names[6459] = "duomilliacendoquinquagintillion"
+number_names[6462] = "duomilliacentrequinquagintillion"
+number_names[6465] = "duomilliacenquattuorquinquagintillion"
+number_names[6468] = "duomilliacenquinquinquagintillion"
+number_names[6471] = "duomilliacensexquinquagintillion"
+number_names[6474] = "duomilliacenseptenquinquagintillion"
+number_names[6477] = "duomilliacenoctoquinquagintillion"
+number_names[6480] = "duomilliacennovemquinquagintillion"
+number_names[6483] = "duomilliacensexagintillion"
+number_names[6486] = "duomilliacenunsexagintillion"
+number_names[6489] = "duomilliacendosexagintillion"
+number_names[6492] = "duomilliacentresexagintillion"
+number_names[6495] = "duomilliacenquattuorsexagintillion"
+number_names[6498] = "duomilliacenquinsexagintillion"
+number_names[6501] = "duomilliacensexsexagintillion"
+number_names[6504] = "duomilliacenseptensexagintillion"
+number_names[6507] = "duomilliacenoctosexagintillion"
+number_names[6510] = "duomilliacennovemsexagintillion"
+number_names[6513] = "duomilliacenseptuagintillion"
+number_names[6516] = "duomilliacenunseptuagintillion"
+number_names[6519] = "duomilliacendoseptuagintillion"
+number_names[6522] = "duomilliacentreseptuagintillion"
+number_names[6525] = "duomilliacenquattuorseptuagintillion"
+number_names[6528] = "duomilliacenquinseptuagintillion"
+number_names[6531] = "duomilliacensexseptuagintillion"
+number_names[6534] = "duomilliacenseptenseptuagintillion"
+number_names[6537] = "duomilliacenoctoseptuagintillion"
+number_names[6540] = "duomilliacennovemseptuagintillion"
+number_names[6543] = "duomilliacenoctogintillion"
+number_names[6546] = "duomilliacenunoctogintillion"
+number_names[6549] = "duomilliacendooctogintillion"
+number_names[6552] = "duomilliacentreoctogintillion"
+number_names[6555] = "duomilliacenquattuoroctogintillion"
+number_names[6558] = "duomilliacenquinoctogintillion"
+number_names[6561] = "duomilliacensexoctogintillion"
+number_names[6564] = "duomilliacenseptenoctogintillion"
+number_names[6567] = "duomilliacenoctooctogintillion"
+number_names[6570] = "duomilliacennovemoctogintillion"
+number_names[6573] = "duomilliacennonagintillion"
+number_names[6576] = "duomilliacenunnonagintillion"
+number_names[6579] = "duomilliacendononagintillion"
+number_names[6582] = "duomilliacentrenonagintillion"
+number_names[6585] = "duomilliacenquattuornonagintillion"
+number_names[6588] = "duomilliacenquinnonagintillion"
+number_names[6591] = "duomilliacensexnonagintillion"
+number_names[6594] = "duomilliacenseptennonagintillion"
+number_names[6597] = "duomilliacenoctononagintillion"
+number_names[6600] = "duomilliacennovemnonagintillion"
+number_names[6603] = "duomilliaducentillion"
+number_names[6606] = "duomilliaducenuntillion"
+number_names[6609] = "duomilliaducendotillion"
+number_names[6612] = "duomilliaducentretillion"
+number_names[6615] = "duomilliaducenquattuortillion"
+number_names[6618] = "duomilliaducenquintillion"
+number_names[6621] = "duomilliaducensextillion"
+number_names[6624] = "duomilliaducenseptentillion"
+number_names[6627] = "duomilliaducenoctotillion"
+number_names[6630] = "duomilliaducennovemtillion"
+number_names[6633] = "duomilliaducendecillion"
+number_names[6636] = "duomilliaducenundecillion"
+number_names[6639] = "duomilliaducendodecillion"
+number_names[6642] = "duomilliaducentredecillion"
+number_names[6645] = "duomilliaducenquattuordecillion"
+number_names[6648] = "duomilliaducenquindecillion"
+number_names[6651] = "duomilliaducensexdecillion"
+number_names[6654] = "duomilliaducenseptendecillion"
+number_names[6657] = "duomilliaducenoctodecillion"
+number_names[6660] = "duomilliaducennovemdecillion"
+number_names[6663] = "duomilliaducenvigintillion"
+number_names[6666] = "duomilliaducenunvigintillion"
+number_names[6669] = "duomilliaducendovigintillion"
+number_names[6672] = "duomilliaducentrevigintillion"
+number_names[6675] = "duomilliaducenquattuorvigintillion"
+number_names[6678] = "duomilliaducenquinvigintillion"
+number_names[6681] = "duomilliaducensexvigintillion"
+number_names[6684] = "duomilliaducenseptenvigintillion"
+number_names[6687] = "duomilliaducenoctovigintillion"
+number_names[6690] = "duomilliaducennovemvigintillion"
+number_names[6693] = "duomilliaducentrigintillion"
+number_names[6696] = "duomilliaducenuntrigintillion"
+number_names[6699] = "duomilliaducendotrigintillion"
+number_names[6702] = "duomilliaducentretrigintillion"
+number_names[6705] = "duomilliaducenquattuortrigintillion"
+number_names[6708] = "duomilliaducenquintrigintillion"
+number_names[6711] = "duomilliaducensextrigintillion"
+number_names[6714] = "duomilliaducenseptentrigintillion"
+number_names[6717] = "duomilliaducenoctotrigintillion"
+number_names[6720] = "duomilliaducennovemtrigintillion"
+number_names[6723] = "duomilliaducenquadragintillion"
+number_names[6726] = "duomilliaducenunquadragintillion"
+number_names[6729] = "duomilliaducendoquadragintillion"
+number_names[6732] = "duomilliaducentrequadragintillion"
+number_names[6735] = "duomilliaducenquattuorquadragintillion"
+number_names[6738] = "duomilliaducenquinquadragintillion"
+number_names[6741] = "duomilliaducensexquadragintillion"
+number_names[6744] = "duomilliaducenseptenquadragintillion"
+number_names[6747] = "duomilliaducenoctoquadragintillion"
+number_names[6750] = "duomilliaducennovemquadragintillion"
+number_names[6753] = "duomilliaducenquinquagintillion"
+number_names[6756] = "duomilliaducenunquinquagintillion"
+number_names[6759] = "duomilliaducendoquinquagintillion"
+number_names[6762] = "duomilliaducentrequinquagintillion"
+number_names[6765] = "duomilliaducenquattuorquinquagintillion"
+number_names[6768] = "duomilliaducenquinquinquagintillion"
+number_names[6771] = "duomilliaducensexquinquagintillion"
+number_names[6774] = "duomilliaducenseptenquinquagintillion"
+number_names[6777] = "duomilliaducenoctoquinquagintillion"
+number_names[6780] = "duomilliaducennovemquinquagintillion"
+number_names[6783] = "duomilliaducensexagintillion"
+number_names[6786] = "duomilliaducenunsexagintillion"
+number_names[6789] = "duomilliaducendosexagintillion"
+number_names[6792] = "duomilliaducentresexagintillion"
+number_names[6795] = "duomilliaducenquattuorsexagintillion"
+number_names[6798] = "duomilliaducenquinsexagintillion"
+number_names[6801] = "duomilliaducensexsexagintillion"
+number_names[6804] = "duomilliaducenseptensexagintillion"
+number_names[6807] = "duomilliaducenoctosexagintillion"
+number_names[6810] = "duomilliaducennovemsexagintillion"
+number_names[6813] = "duomilliaducenseptuagintillion"
+number_names[6816] = "duomilliaducenunseptuagintillion"
+number_names[6819] = "duomilliaducendoseptuagintillion"
+number_names[6822] = "duomilliaducentreseptuagintillion"
+number_names[6825] = "duomilliaducenquattuorseptuagintillion"
+number_names[6828] = "duomilliaducenquinseptuagintillion"
+number_names[6831] = "duomilliaducensexseptuagintillion"
+number_names[6834] = "duomilliaducenseptenseptuagintillion"
+number_names[6837] = "duomilliaducenoctoseptuagintillion"
+number_names[6840] = "duomilliaducennovemseptuagintillion"
+number_names[6843] = "duomilliaducenoctogintillion"
+number_names[6846] = "duomilliaducenunoctogintillion"
+number_names[6849] = "duomilliaducendooctogintillion"
+number_names[6852] = "duomilliaducentreoctogintillion"
+number_names[6855] = "duomilliaducenquattuoroctogintillion"
+number_names[6858] = "duomilliaducenquinoctogintillion"
+number_names[6861] = "duomilliaducensexoctogintillion"
+number_names[6864] = "duomilliaducenseptenoctogintillion"
+number_names[6867] = "duomilliaducenoctooctogintillion"
+number_names[6870] = "duomilliaducennovemoctogintillion"
+number_names[6873] = "duomilliaducennonagintillion"
+number_names[6876] = "duomilliaducenunnonagintillion"
+number_names[6879] = "duomilliaducendononagintillion"
+number_names[6882] = "duomilliaducentrenonagintillion"
+number_names[6885] = "duomilliaducenquattuornonagintillion"
+number_names[6888] = "duomilliaducenquinnonagintillion"
+number_names[6891] = "duomilliaducensexnonagintillion"
+number_names[6894] = "duomilliaducenseptennonagintillion"
+number_names[6897] = "duomilliaducenoctononagintillion"
+number_names[6900] = "duomilliaducennovemnonagintillion"
+number_names[6903] = "duomilliatrecentillion"
+number_names[6906] = "duomilliatrecenuntillion"
+number_names[6909] = "duomilliatrecendotillion"
+number_names[6912] = "duomilliatrecentretillion"
+number_names[6915] = "duomilliatrecenquattuortillion"
+number_names[6918] = "duomilliatrecenquintillion"
+number_names[6921] = "duomilliatrecensextillion"
+number_names[6924] = "duomilliatrecenseptentillion"
+number_names[6927] = "duomilliatrecenoctotillion"
+number_names[6930] = "duomilliatrecennovemtillion"
+number_names[6933] = "duomilliatrecendecillion"
+number_names[6936] = "duomilliatrecenundecillion"
+number_names[6939] = "duomilliatrecendodecillion"
+number_names[6942] = "duomilliatrecentredecillion"
+number_names[6945] = "duomilliatrecenquattuordecillion"
+number_names[6948] = "duomilliatrecenquindecillion"
+number_names[6951] = "duomilliatrecensexdecillion"
+number_names[6954] = "duomilliatrecenseptendecillion"
+number_names[6957] = "duomilliatrecenoctodecillion"
+number_names[6960] = "duomilliatrecennovemdecillion"
+number_names[6963] = "duomilliatrecenvigintillion"
+number_names[6966] = "duomilliatrecenunvigintillion"
+number_names[6969] = "duomilliatrecendovigintillion"
+number_names[6972] = "duomilliatrecentrevigintillion"
+number_names[6975] = "duomilliatrecenquattuorvigintillion"
+number_names[6978] = "duomilliatrecenquinvigintillion"
+number_names[6981] = "duomilliatrecensexvigintillion"
+number_names[6984] = "duomilliatrecenseptenvigintillion"
+number_names[6987] = "duomilliatrecenoctovigintillion"
+number_names[6990] = "duomilliatrecennovemvigintillion"
+number_names[6993] = "duomilliatrecentrigintillion"
+number_names[6996] = "duomilliatrecenuntrigintillion"
+number_names[6999] = "duomilliatrecendotrigintillion"
+number_names[7002] = "duomilliatrecentretrigintillion"
+number_names[7005] = "duomilliatrecenquattuortrigintillion"
+number_names[7008] = "duomilliatrecenquintrigintillion"
+number_names[7011] = "duomilliatrecensextrigintillion"
+number_names[7014] = "duomilliatrecenseptentrigintillion"
+number_names[7017] = "duomilliatrecenoctotrigintillion"
+number_names[7020] = "duomilliatrecennovemtrigintillion"
+number_names[7023] = "duomilliatrecenquadragintillion"
+number_names[7026] = "duomilliatrecenunquadragintillion"
+number_names[7029] = "duomilliatrecendoquadragintillion"
+number_names[7032] = "duomilliatrecentrequadragintillion"
+number_names[7035] = "duomilliatrecenquattuorquadragintillion"
+number_names[7038] = "duomilliatrecenquinquadragintillion"
+number_names[7041] = "duomilliatrecensexquadragintillion"
+number_names[7044] = "duomilliatrecenseptenquadragintillion"
+number_names[7047] = "duomilliatrecenoctoquadragintillion"
+number_names[7050] = "duomilliatrecennovemquadragintillion"
+number_names[7053] = "duomilliatrecenquinquagintillion"
+number_names[7056] = "duomilliatrecenunquinquagintillion"
+number_names[7059] = "duomilliatrecendoquinquagintillion"
+number_names[7062] = "duomilliatrecentrequinquagintillion"
+number_names[7065] = "duomilliatrecenquattuorquinquagintillion"
+number_names[7068] = "duomilliatrecenquinquinquagintillion"
+number_names[7071] = "duomilliatrecensexquinquagintillion"
+number_names[7074] = "duomilliatrecenseptenquinquagintillion"
+number_names[7077] = "duomilliatrecenoctoquinquagintillion"
+number_names[7080] = "duomilliatrecennovemquinquagintillion"
+number_names[7083] = "duomilliatrecensexagintillion"
+number_names[7086] = "duomilliatrecenunsexagintillion"
+number_names[7089] = "duomilliatrecendosexagintillion"
+number_names[7092] = "duomilliatrecentresexagintillion"
+number_names[7095] = "duomilliatrecenquattuorsexagintillion"
+number_names[7098] = "duomilliatrecenquinsexagintillion"
+number_names[7101] = "duomilliatrecensexsexagintillion"
+number_names[7104] = "duomilliatrecenseptensexagintillion"
+number_names[7107] = "duomilliatrecenoctosexagintillion"
+number_names[7110] = "duomilliatrecennovemsexagintillion"
+number_names[7113] = "duomilliatrecenseptuagintillion"
+number_names[7116] = "duomilliatrecenunseptuagintillion"
+number_names[7119] = "duomilliatrecendoseptuagintillion"
+number_names[7122] = "duomilliatrecentreseptuagintillion"
+number_names[7125] = "duomilliatrecenquattuorseptuagintillion"
+number_names[7128] = "duomilliatrecenquinseptuagintillion"
+number_names[7131] = "duomilliatrecensexseptuagintillion"
+number_names[7134] = "duomilliatrecenseptenseptuagintillion"
+number_names[7137] = "duomilliatrecenoctoseptuagintillion"
+number_names[7140] = "duomilliatrecennovemseptuagintillion"
+number_names[7143] = "duomilliatrecenoctogintillion"
+number_names[7146] = "duomilliatrecenunoctogintillion"
+number_names[7149] = "duomilliatrecendooctogintillion"
+number_names[7152] = "duomilliatrecentreoctogintillion"
+number_names[7155] = "duomilliatrecenquattuoroctogintillion"
+number_names[7158] = "duomilliatrecenquinoctogintillion"
+number_names[7161] = "duomilliatrecensexoctogintillion"
+number_names[7164] = "duomilliatrecenseptenoctogintillion"
+number_names[7167] = "duomilliatrecenoctooctogintillion"
+number_names[7170] = "duomilliatrecennovemoctogintillion"
+number_names[7173] = "duomilliatrecennonagintillion"
+number_names[7176] = "duomilliatrecenunnonagintillion"
+number_names[7179] = "duomilliatrecendononagintillion"
+number_names[7182] = "duomilliatrecentrenonagintillion"
+number_names[7185] = "duomilliatrecenquattuornonagintillion"
+number_names[7188] = "duomilliatrecenquinnonagintillion"
+number_names[7191] = "duomilliatrecensexnonagintillion"
+number_names[7194] = "duomilliatrecenseptennonagintillion"
+number_names[7197] = "duomilliatrecenoctononagintillion"
+number_names[7200] = "duomilliatrecennovemnonagintillion"
+number_names[7203] = "duomilliaquadringentillion"
+number_names[7206] = "duomilliaquadringenuntillion"
+number_names[7209] = "duomilliaquadringendotillion"
+number_names[7212] = "duomilliaquadringentretillion"
+number_names[7215] = "duomilliaquadringenquattuortillion"
+number_names[7218] = "duomilliaquadringenquintillion"
+number_names[7221] = "duomilliaquadringensextillion"
+number_names[7224] = "duomilliaquadringenseptentillion"
+number_names[7227] = "duomilliaquadringenoctotillion"
+number_names[7230] = "duomilliaquadringennovemtillion"
+number_names[7233] = "duomilliaquadringendecillion"
+number_names[7236] = "duomilliaquadringenundecillion"
+number_names[7239] = "duomilliaquadringendodecillion"
+number_names[7242] = "duomilliaquadringentredecillion"
+number_names[7245] = "duomilliaquadringenquattuordecillion"
+number_names[7248] = "duomilliaquadringenquindecillion"
+number_names[7251] = "duomilliaquadringensexdecillion"
+number_names[7254] = "duomilliaquadringenseptendecillion"
+number_names[7257] = "duomilliaquadringenoctodecillion"
+number_names[7260] = "duomilliaquadringennovemdecillion"
+number_names[7263] = "duomilliaquadringenvigintillion"
+number_names[7266] = "duomilliaquadringenunvigintillion"
+number_names[7269] = "duomilliaquadringendovigintillion"
+number_names[7272] = "duomilliaquadringentrevigintillion"
+number_names[7275] = "duomilliaquadringenquattuorvigintillion"
+number_names[7278] = "duomilliaquadringenquinvigintillion"
+number_names[7281] = "duomilliaquadringensexvigintillion"
+number_names[7284] = "duomilliaquadringenseptenvigintillion"
+number_names[7287] = "duomilliaquadringenoctovigintillion"
+number_names[7290] = "duomilliaquadringennovemvigintillion"
+number_names[7293] = "duomilliaquadringentrigintillion"
+number_names[7296] = "duomilliaquadringenuntrigintillion"
+number_names[7299] = "duomilliaquadringendotrigintillion"
+number_names[7302] = "duomilliaquadringentretrigintillion"
+number_names[7305] = "duomilliaquadringenquattuortrigintillion"
+number_names[7308] = "duomilliaquadringenquintrigintillion"
+number_names[7311] = "duomilliaquadringensextrigintillion"
+number_names[7314] = "duomilliaquadringenseptentrigintillion"
+number_names[7317] = "duomilliaquadringenoctotrigintillion"
+number_names[7320] = "duomilliaquadringennovemtrigintillion"
+number_names[7323] = "duomilliaquadringenquadragintillion"
+number_names[7326] = "duomilliaquadringenunquadragintillion"
+number_names[7329] = "duomilliaquadringendoquadragintillion"
+number_names[7332] = "duomilliaquadringentrequadragintillion"
+number_names[7335] = "duomilliaquadringenquattuorquadragintillion"
+number_names[7338] = "duomilliaquadringenquinquadragintillion"
+number_names[7341] = "duomilliaquadringensexquadragintillion"
+number_names[7344] = "duomilliaquadringenseptenquadragintillion"
+number_names[7347] = "duomilliaquadringenoctoquadragintillion"
+number_names[7350] = "duomilliaquadringennovemquadragintillion"
+number_names[7353] = "duomilliaquadringenquinquagintillion"
+number_names[7356] = "duomilliaquadringenunquinquagintillion"
+number_names[7359] = "duomilliaquadringendoquinquagintillion"
+number_names[7362] = "duomilliaquadringentrequinquagintillion"
+number_names[7365] = "duomilliaquadringenquattuorquinquagintillion"
+number_names[7368] = "duomilliaquadringenquinquinquagintillion"
+number_names[7371] = "duomilliaquadringensexquinquagintillion"
+number_names[7374] = "duomilliaquadringenseptenquinquagintillion"
+number_names[7377] = "duomilliaquadringenoctoquinquagintillion"
+number_names[7380] = "duomilliaquadringennovemquinquagintillion"
+number_names[7383] = "duomilliaquadringensexagintillion"
+number_names[7386] = "duomilliaquadringenunsexagintillion"
+number_names[7389] = "duomilliaquadringendosexagintillion"
+number_names[7392] = "duomilliaquadringentresexagintillion"
+number_names[7395] = "duomilliaquadringenquattuorsexagintillion"
+number_names[7398] = "duomilliaquadringenquinsexagintillion"
+number_names[7401] = "duomilliaquadringensexsexagintillion"
+number_names[7404] = "duomilliaquadringenseptensexagintillion"
+number_names[7407] = "duomilliaquadringenoctosexagintillion"
+number_names[7410] = "duomilliaquadringennovemsexagintillion"
+number_names[7413] = "duomilliaquadringenseptuagintillion"
+number_names[7416] = "duomilliaquadringenunseptuagintillion"
+number_names[7419] = "duomilliaquadringendoseptuagintillion"
+number_names[7422] = "duomilliaquadringentreseptuagintillion"
+number_names[7425] = "duomilliaquadringenquattuorseptuagintillion"
+number_names[7428] = "duomilliaquadringenquinseptuagintillion"
+number_names[7431] = "duomilliaquadringensexseptuagintillion"
+number_names[7434] = "duomilliaquadringenseptenseptuagintillion"
+number_names[7437] = "duomilliaquadringenoctoseptuagintillion"
+number_names[7440] = "duomilliaquadringennovemseptuagintillion"
+number_names[7443] = "duomilliaquadringenoctogintillion"
+number_names[7446] = "duomilliaquadringenunoctogintillion"
+number_names[7449] = "duomilliaquadringendooctogintillion"
+number_names[7452] = "duomilliaquadringentreoctogintillion"
+number_names[7455] = "duomilliaquadringenquattuoroctogintillion"
+number_names[7458] = "duomilliaquadringenquinoctogintillion"
+number_names[7461] = "duomilliaquadringensexoctogintillion"
+number_names[7464] = "duomilliaquadringenseptenoctogintillion"
+number_names[7467] = "duomilliaquadringenoctooctogintillion"
+number_names[7470] = "duomilliaquadringennovemoctogintillion"
+number_names[7473] = "duomilliaquadringennonagintillion"
+number_names[7476] = "duomilliaquadringenunnonagintillion"
+number_names[7479] = "duomilliaquadringendononagintillion"
+number_names[7482] = "duomilliaquadringentrenonagintillion"
+number_names[7485] = "duomilliaquadringenquattuornonagintillion"
+number_names[7488] = "duomilliaquadringenquinnonagintillion"
+number_names[7491] = "duomilliaquadringensexnonagintillion"
+number_names[7494] = "duomilliaquadringenseptennonagintillion"
+number_names[7497] = "duomilliaquadringenoctononagintillion"
+number_names[7500] = "duomilliaquadringennovemnonagintillion"
+number_names[7503] = "duomilliaquingentillion"
+number_names[7506] = "duomilliaquingenuntillion"
+number_names[7509] = "duomilliaquingendotillion"
+number_names[7512] = "duomilliaquingentretillion"
+number_names[7515] = "duomilliaquingenquattuortillion"
+number_names[7518] = "duomilliaquingenquintillion"
+number_names[7521] = "duomilliaquingensextillion"
+number_names[7524] = "duomilliaquingenseptentillion"
+number_names[7527] = "duomilliaquingenoctotillion"
+number_names[7530] = "duomilliaquingennovemtillion"
+number_names[7533] = "duomilliaquingendecillion"
+number_names[7536] = "duomilliaquingenundecillion"
+number_names[7539] = "duomilliaquingendodecillion"
+number_names[7542] = "duomilliaquingentredecillion"
+number_names[7545] = "duomilliaquingenquattuordecillion"
+number_names[7548] = "duomilliaquingenquindecillion"
+number_names[7551] = "duomilliaquingensexdecillion"
+number_names[7554] = "duomilliaquingenseptendecillion"
+number_names[7557] = "duomilliaquingenoctodecillion"
+number_names[7560] = "duomilliaquingennovemdecillion"
+number_names[7563] = "duomilliaquingenvigintillion"
+number_names[7566] = "duomilliaquingenunvigintillion"
+number_names[7569] = "duomilliaquingendovigintillion"
+number_names[7572] = "duomilliaquingentrevigintillion"
+number_names[7575] = "duomilliaquingenquattuorvigintillion"
+number_names[7578] = "duomilliaquingenquinvigintillion"
+number_names[7581] = "duomilliaquingensexvigintillion"
+number_names[7584] = "duomilliaquingenseptenvigintillion"
+number_names[7587] = "duomilliaquingenoctovigintillion"
+number_names[7590] = "duomilliaquingennovemvigintillion"
+number_names[7593] = "duomilliaquingentrigintillion"
+number_names[7596] = "duomilliaquingenuntrigintillion"
+number_names[7599] = "duomilliaquingendotrigintillion"
+number_names[7602] = "duomilliaquingentretrigintillion"
+number_names[7605] = "duomilliaquingenquattuortrigintillion"
+number_names[7608] = "duomilliaquingenquintrigintillion"
+number_names[7611] = "duomilliaquingensextrigintillion"
+number_names[7614] = "duomilliaquingenseptentrigintillion"
+number_names[7617] = "duomilliaquingenoctotrigintillion"
+number_names[7620] = "duomilliaquingennovemtrigintillion"
+number_names[7623] = "duomilliaquingenquadragintillion"
+number_names[7626] = "duomilliaquingenunquadragintillion"
+number_names[7629] = "duomilliaquingendoquadragintillion"
+number_names[7632] = "duomilliaquingentrequadragintillion"
+number_names[7635] = "duomilliaquingenquattuorquadragintillion"
+number_names[7638] = "duomilliaquingenquinquadragintillion"
+number_names[7641] = "duomilliaquingensexquadragintillion"
+number_names[7644] = "duomilliaquingenseptenquadragintillion"
+number_names[7647] = "duomilliaquingenoctoquadragintillion"
+number_names[7650] = "duomilliaquingennovemquadragintillion"
+number_names[7653] = "duomilliaquingenquinquagintillion"
+number_names[7656] = "duomilliaquingenunquinquagintillion"
+number_names[7659] = "duomilliaquingendoquinquagintillion"
+number_names[7662] = "duomilliaquingentrequinquagintillion"
+number_names[7665] = "duomilliaquingenquattuorquinquagintillion"
+number_names[7668] = "duomilliaquingenquinquinquagintillion"
+number_names[7671] = "duomilliaquingensexquinquagintillion"
+number_names[7674] = "duomilliaquingenseptenquinquagintillion"
+number_names[7677] = "duomilliaquingenoctoquinquagintillion"
+number_names[7680] = "duomilliaquingennovemquinquagintillion"
+number_names[7683] = "duomilliaquingensexagintillion"
+number_names[7686] = "duomilliaquingenunsexagintillion"
+number_names[7689] = "duomilliaquingendosexagintillion"
+number_names[7692] = "duomilliaquingentresexagintillion"
+number_names[7695] = "duomilliaquingenquattuorsexagintillion"
+number_names[7698] = "duomilliaquingenquinsexagintillion"
+number_names[7701] = "duomilliaquingensexsexagintillion"
+number_names[7704] = "duomilliaquingenseptensexagintillion"
+number_names[7707] = "duomilliaquingenoctosexagintillion"
+number_names[7710] = "duomilliaquingennovemsexagintillion"
+number_names[7713] = "duomilliaquingenseptuagintillion"
+number_names[7716] = "duomilliaquingenunseptuagintillion"
+number_names[7719] = "duomilliaquingendoseptuagintillion"
+number_names[7722] = "duomilliaquingentreseptuagintillion"
+number_names[7725] = "duomilliaquingenquattuorseptuagintillion"
+number_names[7728] = "duomilliaquingenquinseptuagintillion"
+number_names[7731] = "duomilliaquingensexseptuagintillion"
+number_names[7734] = "duomilliaquingenseptenseptuagintillion"
+number_names[7737] = "duomilliaquingenoctoseptuagintillion"
+number_names[7740] = "duomilliaquingennovemseptuagintillion"
+number_names[7743] = "duomilliaquingenoctogintillion"
+number_names[7746] = "duomilliaquingenunoctogintillion"
+number_names[7749] = "duomilliaquingendooctogintillion"
+number_names[7752] = "duomilliaquingentreoctogintillion"
+number_names[7755] = "duomilliaquingenquattuoroctogintillion"
+number_names[7758] = "duomilliaquingenquinoctogintillion"
+number_names[7761] = "duomilliaquingensexoctogintillion"
+number_names[7764] = "duomilliaquingenseptenoctogintillion"
+number_names[7767] = "duomilliaquingenoctooctogintillion"
+number_names[7770] = "duomilliaquingennovemoctogintillion"
+number_names[7773] = "duomilliaquingennonagintillion"
+number_names[7776] = "duomilliaquingenunnonagintillion"
+number_names[7779] = "duomilliaquingendononagintillion"
+number_names[7782] = "duomilliaquingentrenonagintillion"
+number_names[7785] = "duomilliaquingenquattuornonagintillion"
+number_names[7788] = "duomilliaquingenquinnonagintillion"
+number_names[7791] = "duomilliaquingensexnonagintillion"
+number_names[7794] = "duomilliaquingenseptennonagintillion"
+number_names[7797] = "duomilliaquingenoctononagintillion"
+number_names[7800] = "duomilliaquingennovemnonagintillion"
+number_names[7803] = "duomilliasescentillion"
+number_names[7806] = "duomilliasescenuntillion"
+number_names[7809] = "duomilliasescendotillion"
+number_names[7812] = "duomilliasescentretillion"
+number_names[7815] = "duomilliasescenquattuortillion"
+number_names[7818] = "duomilliasescenquintillion"
+number_names[7821] = "duomilliasescensextillion"
+number_names[7824] = "duomilliasescenseptentillion"
+number_names[7827] = "duomilliasescenoctotillion"
+number_names[7830] = "duomilliasescennovemtillion"
+number_names[7833] = "duomilliasescendecillion"
+number_names[7836] = "duomilliasescenundecillion"
+number_names[7839] = "duomilliasescendodecillion"
+number_names[7842] = "duomilliasescentredecillion"
+number_names[7845] = "duomilliasescenquattuordecillion"
+number_names[7848] = "duomilliasescenquindecillion"
+number_names[7851] = "duomilliasescensexdecillion"
+number_names[7854] = "duomilliasescenseptendecillion"
+number_names[7857] = "duomilliasescenoctodecillion"
+number_names[7860] = "duomilliasescennovemdecillion"
+number_names[7863] = "duomilliasescenvigintillion"
+number_names[7866] = "duomilliasescenunvigintillion"
+number_names[7869] = "duomilliasescendovigintillion"
+number_names[7872] = "duomilliasescentrevigintillion"
+number_names[7875] = "duomilliasescenquattuorvigintillion"
+number_names[7878] = "duomilliasescenquinvigintillion"
+number_names[7881] = "duomilliasescensexvigintillion"
+number_names[7884] = "duomilliasescenseptenvigintillion"
+number_names[7887] = "duomilliasescenoctovigintillion"
+number_names[7890] = "duomilliasescennovemvigintillion"
+number_names[7893] = "duomilliasescentrigintillion"
+number_names[7896] = "duomilliasescenuntrigintillion"
+number_names[7899] = "duomilliasescendotrigintillion"
+number_names[7902] = "duomilliasescentretrigintillion"
+number_names[7905] = "duomilliasescenquattuortrigintillion"
+number_names[7908] = "duomilliasescenquintrigintillion"
+number_names[7911] = "duomilliasescensextrigintillion"
+number_names[7914] = "duomilliasescenseptentrigintillion"
+number_names[7917] = "duomilliasescenoctotrigintillion"
+number_names[7920] = "duomilliasescennovemtrigintillion"
+number_names[7923] = "duomilliasescenquadragintillion"
+number_names[7926] = "duomilliasescenunquadragintillion"
+number_names[7929] = "duomilliasescendoquadragintillion"
+number_names[7932] = "duomilliasescentrequadragintillion"
+number_names[7935] = "duomilliasescenquattuorquadragintillion"
+number_names[7938] = "duomilliasescenquinquadragintillion"
+number_names[7941] = "duomilliasescensexquadragintillion"
+number_names[7944] = "duomilliasescenseptenquadragintillion"
+number_names[7947] = "duomilliasescenoctoquadragintillion"
+number_names[7950] = "duomilliasescennovemquadragintillion"
+number_names[7953] = "duomilliasescenquinquagintillion"
+number_names[7956] = "duomilliasescenunquinquagintillion"
+number_names[7959] = "duomilliasescendoquinquagintillion"
+number_names[7962] = "duomilliasescentrequinquagintillion"
+number_names[7965] = "duomilliasescenquattuorquinquagintillion"
+number_names[7968] = "duomilliasescenquinquinquagintillion"
+number_names[7971] = "duomilliasescensexquinquagintillion"
+number_names[7974] = "duomilliasescenseptenquinquagintillion"
+number_names[7977] = "duomilliasescenoctoquinquagintillion"
+number_names[7980] = "duomilliasescennovemquinquagintillion"
+number_names[7983] = "duomilliasescensexagintillion"
+number_names[7986] = "duomilliasescenunsexagintillion"
+number_names[7989] = "duomilliasescendosexagintillion"
+number_names[7992] = "duomilliasescentresexagintillion"
+number_names[7995] = "duomilliasescenquattuorsexagintillion"
+number_names[7998] = "duomilliasescenquinsexagintillion"
+number_names[8001] = "duomilliasescensexsexagintillion"
+number_names[8004] = "duomilliasescenseptensexagintillion"
+number_names[8007] = "duomilliasescenoctosexagintillion"
+number_names[8010] = "duomilliasescennovemsexagintillion"
+number_names[8013] = "duomilliasescenseptuagintillion"
+number_names[8016] = "duomilliasescenunseptuagintillion"
+number_names[8019] = "duomilliasescendoseptuagintillion"
+number_names[8022] = "duomilliasescentreseptuagintillion"
+number_names[8025] = "duomilliasescenquattuorseptuagintillion"
+number_names[8028] = "duomilliasescenquinseptuagintillion"
+number_names[8031] = "duomilliasescensexseptuagintillion"
+number_names[8034] = "duomilliasescenseptenseptuagintillion"
+number_names[8037] = "duomilliasescenoctoseptuagintillion"
+number_names[8040] = "duomilliasescennovemseptuagintillion"
+number_names[8043] = "duomilliasescenoctogintillion"
+number_names[8046] = "duomilliasescenunoctogintillion"
+number_names[8049] = "duomilliasescendooctogintillion"
+number_names[8052] = "duomilliasescentreoctogintillion"
+number_names[8055] = "duomilliasescenquattuoroctogintillion"
+number_names[8058] = "duomilliasescenquinoctogintillion"
+number_names[8061] = "duomilliasescensexoctogintillion"
+number_names[8064] = "duomilliasescenseptenoctogintillion"
+number_names[8067] = "duomilliasescenoctooctogintillion"
+number_names[8070] = "duomilliasescennovemoctogintillion"
+number_names[8073] = "duomilliasescennonagintillion"
+number_names[8076] = "duomilliasescenunnonagintillion"
+number_names[8079] = "duomilliasescendononagintillion"
+number_names[8082] = "duomilliasescentrenonagintillion"
+number_names[8085] = "duomilliasescenquattuornonagintillion"
+number_names[8088] = "duomilliasescenquinnonagintillion"
+number_names[8091] = "duomilliasescensexnonagintillion"
+number_names[8094] = "duomilliasescenseptennonagintillion"
+number_names[8097] = "duomilliasescenoctononagintillion"
+number_names[8100] = "duomilliasescennovemnonagintillion"
+number_names[8103] = "duomilliaseptingentillion"
+number_names[8106] = "duomilliaseptingenuntillion"
+number_names[8109] = "duomilliaseptingendotillion"
+number_names[8112] = "duomilliaseptingentretillion"
+number_names[8115] = "duomilliaseptingenquattuortillion"
+number_names[8118] = "duomilliaseptingenquintillion"
+number_names[8121] = "duomilliaseptingensextillion"
+number_names[8124] = "duomilliaseptingenseptentillion"
+number_names[8127] = "duomilliaseptingenoctotillion"
+number_names[8130] = "duomilliaseptingennovemtillion"
+number_names[8133] = "duomilliaseptingendecillion"
+number_names[8136] = "duomilliaseptingenundecillion"
+number_names[8139] = "duomilliaseptingendodecillion"
+number_names[8142] = "duomilliaseptingentredecillion"
+number_names[8145] = "duomilliaseptingenquattuordecillion"
+number_names[8148] = "duomilliaseptingenquindecillion"
+number_names[8151] = "duomilliaseptingensexdecillion"
+number_names[8154] = "duomilliaseptingenseptendecillion"
+number_names[8157] = "duomilliaseptingenoctodecillion"
+number_names[8160] = "duomilliaseptingennovemdecillion"
+number_names[8163] = "duomilliaseptingenvigintillion"
+number_names[8166] = "duomilliaseptingenunvigintillion"
+number_names[8169] = "duomilliaseptingendovigintillion"
+number_names[8172] = "duomilliaseptingentrevigintillion"
+number_names[8175] = "duomilliaseptingenquattuorvigintillion"
+number_names[8178] = "duomilliaseptingenquinvigintillion"
+number_names[8181] = "duomilliaseptingensexvigintillion"
+number_names[8184] = "duomilliaseptingenseptenvigintillion"
+number_names[8187] = "duomilliaseptingenoctovigintillion"
+number_names[8190] = "duomilliaseptingennovemvigintillion"
+number_names[8193] = "duomilliaseptingentrigintillion"
+number_names[8196] = "duomilliaseptingenuntrigintillion"
+number_names[8199] = "duomilliaseptingendotrigintillion"
+number_names[8202] = "duomilliaseptingentretrigintillion"
+number_names[8205] = "duomilliaseptingenquattuortrigintillion"
+number_names[8208] = "duomilliaseptingenquintrigintillion"
+number_names[8211] = "duomilliaseptingensextrigintillion"
+number_names[8214] = "duomilliaseptingenseptentrigintillion"
+number_names[8217] = "duomilliaseptingenoctotrigintillion"
+number_names[8220] = "duomilliaseptingennovemtrigintillion"
+number_names[8223] = "duomilliaseptingenquadragintillion"
+number_names[8226] = "duomilliaseptingenunquadragintillion"
+number_names[8229] = "duomilliaseptingendoquadragintillion"
+number_names[8232] = "duomilliaseptingentrequadragintillion"
+number_names[8235] = "duomilliaseptingenquattuorquadragintillion"
+number_names[8238] = "duomilliaseptingenquinquadragintillion"
+number_names[8241] = "duomilliaseptingensexquadragintillion"
+number_names[8244] = "duomilliaseptingenseptenquadragintillion"
+number_names[8247] = "duomilliaseptingenoctoquadragintillion"
+number_names[8250] = "duomilliaseptingennovemquadragintillion"
+number_names[8253] = "duomilliaseptingenquinquagintillion"
+number_names[8256] = "duomilliaseptingenunquinquagintillion"
+number_names[8259] = "duomilliaseptingendoquinquagintillion"
+number_names[8262] = "duomilliaseptingentrequinquagintillion"
+number_names[8265] = "duomilliaseptingenquattuorquinquagintillion"
+number_names[8268] = "duomilliaseptingenquinquinquagintillion"
+number_names[8271] = "duomilliaseptingensexquinquagintillion"
+number_names[8274] = "duomilliaseptingenseptenquinquagintillion"
+number_names[8277] = "duomilliaseptingenoctoquinquagintillion"
+number_names[8280] = "duomilliaseptingennovemquinquagintillion"
+number_names[8283] = "duomilliaseptingensexagintillion"
+number_names[8286] = "duomilliaseptingenunsexagintillion"
+number_names[8289] = "duomilliaseptingendosexagintillion"
+number_names[8292] = "duomilliaseptingentresexagintillion"
+number_names[8295] = "duomilliaseptingenquattuorsexagintillion"
+number_names[8298] = "duomilliaseptingenquinsexagintillion"
+number_names[8301] = "duomilliaseptingensexsexagintillion"
+number_names[8304] = "duomilliaseptingenseptensexagintillion"
+number_names[8307] = "duomilliaseptingenoctosexagintillion"
+number_names[8310] = "duomilliaseptingennovemsexagintillion"
+number_names[8313] = "duomilliaseptingenseptuagintillion"
+number_names[8316] = "duomilliaseptingenunseptuagintillion"
+number_names[8319] = "duomilliaseptingendoseptuagintillion"
+number_names[8322] = "duomilliaseptingentreseptuagintillion"
+number_names[8325] = "duomilliaseptingenquattuorseptuagintillion"
+number_names[8328] = "duomilliaseptingenquinseptuagintillion"
+number_names[8331] = "duomilliaseptingensexseptuagintillion"
+number_names[8334] = "duomilliaseptingenseptenseptuagintillion"
+number_names[8337] = "duomilliaseptingenoctoseptuagintillion"
+number_names[8340] = "duomilliaseptingennovemseptuagintillion"
+number_names[8343] = "duomilliaseptingenoctogintillion"
+number_names[8346] = "duomilliaseptingenunoctogintillion"
+number_names[8349] = "duomilliaseptingendooctogintillion"
+number_names[8352] = "duomilliaseptingentreoctogintillion"
+number_names[8355] = "duomilliaseptingenquattuoroctogintillion"
+number_names[8358] = "duomilliaseptingenquinoctogintillion"
+number_names[8361] = "duomilliaseptingensexoctogintillion"
+number_names[8364] = "duomilliaseptingenseptenoctogintillion"
+number_names[8367] = "duomilliaseptingenoctooctogintillion"
+number_names[8370] = "duomilliaseptingennovemoctogintillion"
+number_names[8373] = "duomilliaseptingennonagintillion"
+number_names[8376] = "duomilliaseptingenunnonagintillion"
+number_names[8379] = "duomilliaseptingendononagintillion"
+number_names[8382] = "duomilliaseptingentrenonagintillion"
+number_names[8385] = "duomilliaseptingenquattuornonagintillion"
+number_names[8388] = "duomilliaseptingenquinnonagintillion"
+number_names[8391] = "duomilliaseptingensexnonagintillion"
+number_names[8394] = "duomilliaseptingenseptennonagintillion"
+number_names[8397] = "duomilliaseptingenoctononagintillion"
+number_names[8400] = "duomilliaseptingennovemnonagintillion"
+number_names[8403] = "duomilliaoctingentillion"
+number_names[8406] = "duomilliaoctingenuntillion"
+number_names[8409] = "duomilliaoctingendotillion"
+number_names[8412] = "duomilliaoctingentretillion"
+number_names[8415] = "duomilliaoctingenquattuortillion"
+number_names[8418] = "duomilliaoctingenquintillion"
+number_names[8421] = "duomilliaoctingensextillion"
+number_names[8424] = "duomilliaoctingenseptentillion"
+number_names[8427] = "duomilliaoctingenoctotillion"
+number_names[8430] = "duomilliaoctingennovemtillion"
+number_names[8433] = "duomilliaoctingendecillion"
+number_names[8436] = "duomilliaoctingenundecillion"
+number_names[8439] = "duomilliaoctingendodecillion"
+number_names[8442] = "duomilliaoctingentredecillion"
+number_names[8445] = "duomilliaoctingenquattuordecillion"
+number_names[8448] = "duomilliaoctingenquindecillion"
+number_names[8451] = "duomilliaoctingensexdecillion"
+number_names[8454] = "duomilliaoctingenseptendecillion"
+number_names[8457] = "duomilliaoctingenoctodecillion"
+number_names[8460] = "duomilliaoctingennovemdecillion"
+number_names[8463] = "duomilliaoctingenvigintillion"
+number_names[8466] = "duomilliaoctingenunvigintillion"
+number_names[8469] = "duomilliaoctingendovigintillion"
+number_names[8472] = "duomilliaoctingentrevigintillion"
+number_names[8475] = "duomilliaoctingenquattuorvigintillion"
+number_names[8478] = "duomilliaoctingenquinvigintillion"
+number_names[8481] = "duomilliaoctingensexvigintillion"
+number_names[8484] = "duomilliaoctingenseptenvigintillion"
+number_names[8487] = "duomilliaoctingenoctovigintillion"
+number_names[8490] = "duomilliaoctingennovemvigintillion"
+number_names[8493] = "duomilliaoctingentrigintillion"
+number_names[8496] = "duomilliaoctingenuntrigintillion"
+number_names[8499] = "duomilliaoctingendotrigintillion"
+number_names[8502] = "duomilliaoctingentretrigintillion"
+number_names[8505] = "duomilliaoctingenquattuortrigintillion"
+number_names[8508] = "duomilliaoctingenquintrigintillion"
+number_names[8511] = "duomilliaoctingensextrigintillion"
+number_names[8514] = "duomilliaoctingenseptentrigintillion"
+number_names[8517] = "duomilliaoctingenoctotrigintillion"
+number_names[8520] = "duomilliaoctingennovemtrigintillion"
+number_names[8523] = "duomilliaoctingenquadragintillion"
+number_names[8526] = "duomilliaoctingenunquadragintillion"
+number_names[8529] = "duomilliaoctingendoquadragintillion"
+number_names[8532] = "duomilliaoctingentrequadragintillion"
+number_names[8535] = "duomilliaoctingenquattuorquadragintillion"
+number_names[8538] = "duomilliaoctingenquinquadragintillion"
+number_names[8541] = "duomilliaoctingensexquadragintillion"
+number_names[8544] = "duomilliaoctingenseptenquadragintillion"
+number_names[8547] = "duomilliaoctingenoctoquadragintillion"
+number_names[8550] = "duomilliaoctingennovemquadragintillion"
+number_names[8553] = "duomilliaoctingenquinquagintillion"
+number_names[8556] = "duomilliaoctingenunquinquagintillion"
+number_names[8559] = "duomilliaoctingendoquinquagintillion"
+number_names[8562] = "duomilliaoctingentrequinquagintillion"
+number_names[8565] = "duomilliaoctingenquattuorquinquagintillion"
+number_names[8568] = "duomilliaoctingenquinquinquagintillion"
+number_names[8571] = "duomilliaoctingensexquinquagintillion"
+number_names[8574] = "duomilliaoctingenseptenquinquagintillion"
+number_names[8577] = "duomilliaoctingenoctoquinquagintillion"
+number_names[8580] = "duomilliaoctingennovemquinquagintillion"
+number_names[8583] = "duomilliaoctingensexagintillion"
+number_names[8586] = "duomilliaoctingenunsexagintillion"
+number_names[8589] = "duomilliaoctingendosexagintillion"
+number_names[8592] = "duomilliaoctingentresexagintillion"
+number_names[8595] = "duomilliaoctingenquattuorsexagintillion"
+number_names[8598] = "duomilliaoctingenquinsexagintillion"
+number_names[8601] = "duomilliaoctingensexsexagintillion"
+number_names[8604] = "duomilliaoctingenseptensexagintillion"
+number_names[8607] = "duomilliaoctingenoctosexagintillion"
+number_names[8610] = "duomilliaoctingennovemsexagintillion"
+number_names[8613] = "duomilliaoctingenseptuagintillion"
+number_names[8616] = "duomilliaoctingenunseptuagintillion"
+number_names[8619] = "duomilliaoctingendoseptuagintillion"
+number_names[8622] = "duomilliaoctingentreseptuagintillion"
+number_names[8625] = "duomilliaoctingenquattuorseptuagintillion"
+number_names[8628] = "duomilliaoctingenquinseptuagintillion"
+number_names[8631] = "duomilliaoctingensexseptuagintillion"
+number_names[8634] = "duomilliaoctingenseptenseptuagintillion"
+number_names[8637] = "duomilliaoctingenoctoseptuagintillion"
+number_names[8640] = "duomilliaoctingennovemseptuagintillion"
+number_names[8643] = "duomilliaoctingenoctogintillion"
+number_names[8646] = "duomilliaoctingenunoctogintillion"
+number_names[8649] = "duomilliaoctingendooctogintillion"
+number_names[8652] = "duomilliaoctingentreoctogintillion"
+number_names[8655] = "duomilliaoctingenquattuoroctogintillion"
+number_names[8658] = "duomilliaoctingenquinoctogintillion"
+number_names[8661] = "duomilliaoctingensexoctogintillion"
+number_names[8664] = "duomilliaoctingenseptenoctogintillion"
+number_names[8667] = "duomilliaoctingenoctooctogintillion"
+number_names[8670] = "duomilliaoctingennovemoctogintillion"
+number_names[8673] = "duomilliaoctingennonagintillion"
+number_names[8676] = "duomilliaoctingenunnonagintillion"
+number_names[8679] = "duomilliaoctingendononagintillion"
+number_names[8682] = "duomilliaoctingentrenonagintillion"
+number_names[8685] = "duomilliaoctingenquattuornonagintillion"
+number_names[8688] = "duomilliaoctingenquinnonagintillion"
+number_names[8691] = "duomilliaoctingensexnonagintillion"
+number_names[8694] = "duomilliaoctingenseptennonagintillion"
+number_names[8697] = "duomilliaoctingenoctononagintillion"
+number_names[8700] = "duomilliaoctingennovemnonagintillion"
+number_names[8703] = "duomillianongentillion"
+number_names[8706] = "duomillianongenuntillion"
+number_names[8709] = "duomillianongendotillion"
+number_names[8712] = "duomillianongentretillion"
+number_names[8715] = "duomillianongenquattuortillion"
+number_names[8718] = "duomillianongenquintillion"
+number_names[8721] = "duomillianongensextillion"
+number_names[8724] = "duomillianongenseptentillion"
+number_names[8727] = "duomillianongenoctotillion"
+number_names[8730] = "duomillianongennovemtillion"
+number_names[8733] = "duomillianongendecillion"
+number_names[8736] = "duomillianongenundecillion"
+number_names[8739] = "duomillianongendodecillion"
+number_names[8742] = "duomillianongentredecillion"
+number_names[8745] = "duomillianongenquattuordecillion"
+number_names[8748] = "duomillianongenquindecillion"
+number_names[8751] = "duomillianongensexdecillion"
+number_names[8754] = "duomillianongenseptendecillion"
+number_names[8757] = "duomillianongenoctodecillion"
+number_names[8760] = "duomillianongennovemdecillion"
+number_names[8763] = "duomillianongenvigintillion"
+number_names[8766] = "duomillianongenunvigintillion"
+number_names[8769] = "duomillianongendovigintillion"
+number_names[8772] = "duomillianongentrevigintillion"
+number_names[8775] = "duomillianongenquattuorvigintillion"
+number_names[8778] = "duomillianongenquinvigintillion"
+number_names[8781] = "duomillianongensexvigintillion"
+number_names[8784] = "duomillianongenseptenvigintillion"
+number_names[8787] = "duomillianongenoctovigintillion"
+number_names[8790] = "duomillianongennovemvigintillion"
+number_names[8793] = "duomillianongentrigintillion"
+number_names[8796] = "duomillianongenuntrigintillion"
+number_names[8799] = "duomillianongendotrigintillion"
+number_names[8802] = "duomillianongentretrigintillion"
+number_names[8805] = "duomillianongenquattuortrigintillion"
+number_names[8808] = "duomillianongenquintrigintillion"
+number_names[8811] = "duomillianongensextrigintillion"
+number_names[8814] = "duomillianongenseptentrigintillion"
+number_names[8817] = "duomillianongenoctotrigintillion"
+number_names[8820] = "duomillianongennovemtrigintillion"
+number_names[8823] = "duomillianongenquadragintillion"
+number_names[8826] = "duomillianongenunquadragintillion"
+number_names[8829] = "duomillianongendoquadragintillion"
+number_names[8832] = "duomillianongentrequadragintillion"
+number_names[8835] = "duomillianongenquattuorquadragintillion"
+number_names[8838] = "duomillianongenquinquadragintillion"
+number_names[8841] = "duomillianongensexquadragintillion"
+number_names[8844] = "duomillianongenseptenquadragintillion"
+number_names[8847] = "duomillianongenoctoquadragintillion"
+number_names[8850] = "duomillianongennovemquadragintillion"
+number_names[8853] = "duomillianongenquinquagintillion"
+number_names[8856] = "duomillianongenunquinquagintillion"
+number_names[8859] = "duomillianongendoquinquagintillion"
+number_names[8862] = "duomillianongentrequinquagintillion"
+number_names[8865] = "duomillianongenquattuorquinquagintillion"
+number_names[8868] = "duomillianongenquinquinquagintillion"
+number_names[8871] = "duomillianongensexquinquagintillion"
+number_names[8874] = "duomillianongenseptenquinquagintillion"
+number_names[8877] = "duomillianongenoctoquinquagintillion"
+number_names[8880] = "duomillianongennovemquinquagintillion"
+number_names[8883] = "duomillianongensexagintillion"
+number_names[8886] = "duomillianongenunsexagintillion"
+number_names[8889] = "duomillianongendosexagintillion"
+number_names[8892] = "duomillianongentresexagintillion"
+number_names[8895] = "duomillianongenquattuorsexagintillion"
+number_names[8898] = "duomillianongenquinsexagintillion"
+number_names[8901] = "duomillianongensexsexagintillion"
+number_names[8904] = "duomillianongenseptensexagintillion"
+number_names[8907] = "duomillianongenoctosexagintillion"
+number_names[8910] = "duomillianongennovemsexagintillion"
+number_names[8913] = "duomillianongenseptuagintillion"
+number_names[8916] = "duomillianongenunseptuagintillion"
+number_names[8919] = "duomillianongendoseptuagintillion"
+number_names[8922] = "duomillianongentreseptuagintillion"
+number_names[8925] = "duomillianongenquattuorseptuagintillion"
+number_names[8928] = "duomillianongenquinseptuagintillion"
+number_names[8931] = "duomillianongensexseptuagintillion"
+number_names[8934] = "duomillianongenseptenseptuagintillion"
+number_names[8937] = "duomillianongenoctoseptuagintillion"
+number_names[8940] = "duomillianongennovemseptuagintillion"
+number_names[8943] = "duomillianongenoctogintillion"
+number_names[8946] = "duomillianongenunoctogintillion"
+number_names[8949] = "duomillianongendooctogintillion"
+number_names[8952] = "duomillianongentreoctogintillion"
+number_names[8955] = "duomillianongenquattuoroctogintillion"
+number_names[8958] = "duomillianongenquinoctogintillion"
+number_names[8961] = "duomillianongensexoctogintillion"
+number_names[8964] = "duomillianongenseptenoctogintillion"
+number_names[8967] = "duomillianongenoctooctogintillion"
+number_names[8970] = "duomillianongennovemoctogintillion"
+number_names[8973] = "duomillianongennonagintillion"
+number_names[8976] = "duomillianongenunnonagintillion"
+number_names[8979] = "duomillianongendononagintillion"
+number_names[8982] = "duomillianongentrenonagintillion"
+number_names[8985] = "duomillianongenquattuornonagintillion"
+number_names[8988] = "duomillianongenquinnonagintillion"
+number_names[8991] = "duomillianongensexnonagintillion"
+number_names[8994] = "duomillianongenseptennonagintillion"
+number_names[8997] = "duomillianongenoctononagintillion"
+number_names[9000] = "duomillianongennovemnonagintillion"
+number_names[9003] = "tremilliatillion"
+number_names[9006] = "tremilliauntillion"
+number_names[9009] = "tremilliadotillion"
+number_names[9012] = "tremilliatretillion"
+number_names[9015] = "tremilliaquattuortillion"
+number_names[9018] = "tremilliaquintillion"
+number_names[9021] = "tremilliasextillion"
+number_names[9024] = "tremilliaseptentillion"
+number_names[9027] = "tremilliaoctotillion"
+number_names[9030] = "tremillianovemtillion"
+number_names[9033] = "tremilliadecillion"
+number_names[9036] = "tremilliaundecillion"
+number_names[9039] = "tremilliadodecillion"
+number_names[9042] = "tremilliatredecillion"
+number_names[9045] = "tremilliaquattuordecillion"
+number_names[9048] = "tremilliaquindecillion"
+number_names[9051] = "tremilliasexdecillion"
+number_names[9054] = "tremilliaseptendecillion"
+number_names[9057] = "tremilliaoctodecillion"
+number_names[9060] = "tremillianovemdecillion"
+number_names[9063] = "tremilliavigintillion"
+number_names[9066] = "tremilliaunvigintillion"
+number_names[9069] = "tremilliadovigintillion"
+number_names[9072] = "tremilliatrevigintillion"
+number_names[9075] = "tremilliaquattuorvigintillion"
+number_names[9078] = "tremilliaquinvigintillion"
+number_names[9081] = "tremilliasexvigintillion"
+number_names[9084] = "tremilliaseptenvigintillion"
+number_names[9087] = "tremilliaoctovigintillion"
+number_names[9090] = "tremillianovemvigintillion"
+number_names[9093] = "tremilliatrigintillion"
+number_names[9096] = "tremilliauntrigintillion"
+number_names[9099] = "tremilliadotrigintillion"
+number_names[9102] = "tremilliatretrigintillion"
+number_names[9105] = "tremilliaquattuortrigintillion"
+number_names[9108] = "tremilliaquintrigintillion"
+number_names[9111] = "tremilliasextrigintillion"
+number_names[9114] = "tremilliaseptentrigintillion"
+number_names[9117] = "tremilliaoctotrigintillion"
+number_names[9120] = "tremillianovemtrigintillion"
+number_names[9123] = "tremilliaquadragintillion"
+number_names[9126] = "tremilliaunquadragintillion"
+number_names[9129] = "tremilliadoquadragintillion"
+number_names[9132] = "tremilliatrequadragintillion"
+number_names[9135] = "tremilliaquattuorquadragintillion"
+number_names[9138] = "tremilliaquinquadragintillion"
+number_names[9141] = "tremilliasexquadragintillion"
+number_names[9144] = "tremilliaseptenquadragintillion"
+number_names[9147] = "tremilliaoctoquadragintillion"
+number_names[9150] = "tremillianovemquadragintillion"
+number_names[9153] = "tremilliaquinquagintillion"
+number_names[9156] = "tremilliaunquinquagintillion"
+number_names[9159] = "tremilliadoquinquagintillion"
+number_names[9162] = "tremilliatrequinquagintillion"
+number_names[9165] = "tremilliaquattuorquinquagintillion"
+number_names[9168] = "tremilliaquinquinquagintillion"
+number_names[9171] = "tremilliasexquinquagintillion"
+number_names[9174] = "tremilliaseptenquinquagintillion"
+number_names[9177] = "tremilliaoctoquinquagintillion"
+number_names[9180] = "tremillianovemquinquagintillion"
+number_names[9183] = "tremilliasexagintillion"
+number_names[9186] = "tremilliaunsexagintillion"
+number_names[9189] = "tremilliadosexagintillion"
+number_names[9192] = "tremilliatresexagintillion"
+number_names[9195] = "tremilliaquattuorsexagintillion"
+number_names[9198] = "tremilliaquinsexagintillion"
+number_names[9201] = "tremilliasexsexagintillion"
+number_names[9204] = "tremilliaseptensexagintillion"
+number_names[9207] = "tremilliaoctosexagintillion"
+number_names[9210] = "tremillianovemsexagintillion"
+number_names[9213] = "tremilliaseptuagintillion"
+number_names[9216] = "tremilliaunseptuagintillion"
+number_names[9219] = "tremilliadoseptuagintillion"
+number_names[9222] = "tremilliatreseptuagintillion"
+number_names[9225] = "tremilliaquattuorseptuagintillion"
+number_names[9228] = "tremilliaquinseptuagintillion"
+number_names[9231] = "tremilliasexseptuagintillion"
+number_names[9234] = "tremilliaseptenseptuagintillion"
+number_names[9237] = "tremilliaoctoseptuagintillion"
+number_names[9240] = "tremillianovemseptuagintillion"
+number_names[9243] = "tremilliaoctogintillion"
+number_names[9246] = "tremilliaunoctogintillion"
+number_names[9249] = "tremilliadooctogintillion"
+number_names[9252] = "tremilliatreoctogintillion"
+number_names[9255] = "tremilliaquattuoroctogintillion"
+number_names[9258] = "tremilliaquinoctogintillion"
+number_names[9261] = "tremilliasexoctogintillion"
+number_names[9264] = "tremilliaseptenoctogintillion"
+number_names[9267] = "tremilliaoctooctogintillion"
+number_names[9270] = "tremillianovemoctogintillion"
+number_names[9273] = "tremillianonagintillion"
+number_names[9276] = "tremilliaunnonagintillion"
+number_names[9279] = "tremilliadononagintillion"
+number_names[9282] = "tremilliatrenonagintillion"
+number_names[9285] = "tremilliaquattuornonagintillion"
+number_names[9288] = "tremilliaquinnonagintillion"
+number_names[9291] = "tremilliasexnonagintillion"
+number_names[9294] = "tremilliaseptennonagintillion"
+number_names[9297] = "tremilliaoctononagintillion"
+number_names[9300] = "tremillianovemnonagintillion"
+number_names[9303] = "tremilliacentillion"
+number_names[9306] = "tremilliacenuntillion"
+number_names[9309] = "tremilliacendotillion"
+number_names[9312] = "tremilliacentretillion"
+number_names[9315] = "tremilliacenquattuortillion"
+number_names[9318] = "tremilliacenquintillion"
+number_names[9321] = "tremilliacensextillion"
+number_names[9324] = "tremilliacenseptentillion"
+number_names[9327] = "tremilliacenoctotillion"
+number_names[9330] = "tremilliacennovemtillion"
+number_names[9333] = "tremilliacendecillion"
+number_names[9336] = "tremilliacenundecillion"
+number_names[9339] = "tremilliacendodecillion"
+number_names[9342] = "tremilliacentredecillion"
+number_names[9345] = "tremilliacenquattuordecillion"
+number_names[9348] = "tremilliacenquindecillion"
+number_names[9351] = "tremilliacensexdecillion"
+number_names[9354] = "tremilliacenseptendecillion"
+number_names[9357] = "tremilliacenoctodecillion"
+number_names[9360] = "tremilliacennovemdecillion"
+number_names[9363] = "tremilliacenvigintillion"
+number_names[9366] = "tremilliacenunvigintillion"
+number_names[9369] = "tremilliacendovigintillion"
+number_names[9372] = "tremilliacentrevigintillion"
+number_names[9375] = "tremilliacenquattuorvigintillion"
+number_names[9378] = "tremilliacenquinvigintillion"
+number_names[9381] = "tremilliacensexvigintillion"
+number_names[9384] = "tremilliacenseptenvigintillion"
+number_names[9387] = "tremilliacenoctovigintillion"
+number_names[9390] = "tremilliacennovemvigintillion"
+number_names[9393] = "tremilliacentrigintillion"
+number_names[9396] = "tremilliacenuntrigintillion"
+number_names[9399] = "tremilliacendotrigintillion"
+number_names[9402] = "tremilliacentretrigintillion"
+number_names[9405] = "tremilliacenquattuortrigintillion"
+number_names[9408] = "tremilliacenquintrigintillion"
+number_names[9411] = "tremilliacensextrigintillion"
+number_names[9414] = "tremilliacenseptentrigintillion"
+number_names[9417] = "tremilliacenoctotrigintillion"
+number_names[9420] = "tremilliacennovemtrigintillion"
+number_names[9423] = "tremilliacenquadragintillion"
+number_names[9426] = "tremilliacenunquadragintillion"
+number_names[9429] = "tremilliacendoquadragintillion"
+number_names[9432] = "tremilliacentrequadragintillion"
+number_names[9435] = "tremilliacenquattuorquadragintillion"
+number_names[9438] = "tremilliacenquinquadragintillion"
+number_names[9441] = "tremilliacensexquadragintillion"
+number_names[9444] = "tremilliacenseptenquadragintillion"
+number_names[9447] = "tremilliacenoctoquadragintillion"
+number_names[9450] = "tremilliacennovemquadragintillion"
+number_names[9453] = "tremilliacenquinquagintillion"
+number_names[9456] = "tremilliacenunquinquagintillion"
+number_names[9459] = "tremilliacendoquinquagintillion"
+number_names[9462] = "tremilliacentrequinquagintillion"
+number_names[9465] = "tremilliacenquattuorquinquagintillion"
+number_names[9468] = "tremilliacenquinquinquagintillion"
+number_names[9471] = "tremilliacensexquinquagintillion"
+number_names[9474] = "tremilliacenseptenquinquagintillion"
+number_names[9477] = "tremilliacenoctoquinquagintillion"
+number_names[9480] = "tremilliacennovemquinquagintillion"
+number_names[9483] = "tremilliacensexagintillion"
+number_names[9486] = "tremilliacenunsexagintillion"
+number_names[9489] = "tremilliacendosexagintillion"
+number_names[9492] = "tremilliacentresexagintillion"
+number_names[9495] = "tremilliacenquattuorsexagintillion"
+number_names[9498] = "tremilliacenquinsexagintillion"
+number_names[9501] = "tremilliacensexsexagintillion"
+number_names[9504] = "tremilliacenseptensexagintillion"
+number_names[9507] = "tremilliacenoctosexagintillion"
+number_names[9510] = "tremilliacennovemsexagintillion"
+number_names[9513] = "tremilliacenseptuagintillion"
+number_names[9516] = "tremilliacenunseptuagintillion"
+number_names[9519] = "tremilliacendoseptuagintillion"
+number_names[9522] = "tremilliacentreseptuagintillion"
+number_names[9525] = "tremilliacenquattuorseptuagintillion"
+number_names[9528] = "tremilliacenquinseptuagintillion"
+number_names[9531] = "tremilliacensexseptuagintillion"
+number_names[9534] = "tremilliacenseptenseptuagintillion"
+number_names[9537] = "tremilliacenoctoseptuagintillion"
+number_names[9540] = "tremilliacennovemseptuagintillion"
+number_names[9543] = "tremilliacenoctogintillion"
+number_names[9546] = "tremilliacenunoctogintillion"
+number_names[9549] = "tremilliacendooctogintillion"
+number_names[9552] = "tremilliacentreoctogintillion"
+number_names[9555] = "tremilliacenquattuoroctogintillion"
+number_names[9558] = "tremilliacenquinoctogintillion"
+number_names[9561] = "tremilliacensexoctogintillion"
+number_names[9564] = "tremilliacenseptenoctogintillion"
+number_names[9567] = "tremilliacenoctooctogintillion"
+number_names[9570] = "tremilliacennovemoctogintillion"
+number_names[9573] = "tremilliacennonagintillion"
+number_names[9576] = "tremilliacenunnonagintillion"
+number_names[9579] = "tremilliacendononagintillion"
+number_names[9582] = "tremilliacentrenonagintillion"
+number_names[9585] = "tremilliacenquattuornonagintillion"
+number_names[9588] = "tremilliacenquinnonagintillion"
+number_names[9591] = "tremilliacensexnonagintillion"
+number_names[9594] = "tremilliacenseptennonagintillion"
+number_names[9597] = "tremilliacenoctononagintillion"
+number_names[9600] = "tremilliacennovemnonagintillion"
+number_names[9603] = "tremilliaducentillion"
+number_names[9606] = "tremilliaducenuntillion"
+number_names[9609] = "tremilliaducendotillion"
+number_names[9612] = "tremilliaducentretillion"
+number_names[9615] = "tremilliaducenquattuortillion"
+number_names[9618] = "tremilliaducenquintillion"
+number_names[9621] = "tremilliaducensextillion"
+number_names[9624] = "tremilliaducenseptentillion"
+number_names[9627] = "tremilliaducenoctotillion"
+number_names[9630] = "tremilliaducennovemtillion"
+number_names[9633] = "tremilliaducendecillion"
+number_names[9636] = "tremilliaducenundecillion"
+number_names[9639] = "tremilliaducendodecillion"
+number_names[9642] = "tremilliaducentredecillion"
+number_names[9645] = "tremilliaducenquattuordecillion"
+number_names[9648] = "tremilliaducenquindecillion"
+number_names[9651] = "tremilliaducensexdecillion"
+number_names[9654] = "tremilliaducenseptendecillion"
+number_names[9657] = "tremilliaducenoctodecillion"
+number_names[9660] = "tremilliaducennovemdecillion"
+number_names[9663] = "tremilliaducenvigintillion"
+number_names[9666] = "tremilliaducenunvigintillion"
+number_names[9669] = "tremilliaducendovigintillion"
+number_names[9672] = "tremilliaducentrevigintillion"
+number_names[9675] = "tremilliaducenquattuorvigintillion"
+number_names[9678] = "tremilliaducenquinvigintillion"
+number_names[9681] = "tremilliaducensexvigintillion"
+number_names[9684] = "tremilliaducenseptenvigintillion"
+number_names[9687] = "tremilliaducenoctovigintillion"
+number_names[9690] = "tremilliaducennovemvigintillion"
+number_names[9693] = "tremilliaducentrigintillion"
+number_names[9696] = "tremilliaducenuntrigintillion"
+number_names[9699] = "tremilliaducendotrigintillion"
+number_names[9702] = "tremilliaducentretrigintillion"
+number_names[9705] = "tremilliaducenquattuortrigintillion"
+number_names[9708] = "tremilliaducenquintrigintillion"
+number_names[9711] = "tremilliaducensextrigintillion"
+number_names[9714] = "tremilliaducenseptentrigintillion"
+number_names[9717] = "tremilliaducenoctotrigintillion"
+number_names[9720] = "tremilliaducennovemtrigintillion"
+number_names[9723] = "tremilliaducenquadragintillion"
+number_names[9726] = "tremilliaducenunquadragintillion"
+number_names[9729] = "tremilliaducendoquadragintillion"
+number_names[9732] = "tremilliaducentrequadragintillion"
+number_names[9735] = "tremilliaducenquattuorquadragintillion"
+number_names[9738] = "tremilliaducenquinquadragintillion"
+number_names[9741] = "tremilliaducensexquadragintillion"
+number_names[9744] = "tremilliaducenseptenquadragintillion"
+number_names[9747] = "tremilliaducenoctoquadragintillion"
+number_names[9750] = "tremilliaducennovemquadragintillion"
+number_names[9753] = "tremilliaducenquinquagintillion"
+number_names[9756] = "tremilliaducenunquinquagintillion"
+number_names[9759] = "tremilliaducendoquinquagintillion"
+number_names[9762] = "tremilliaducentrequinquagintillion"
+number_names[9765] = "tremilliaducenquattuorquinquagintillion"
+number_names[9768] = "tremilliaducenquinquinquagintillion"
+number_names[9771] = "tremilliaducensexquinquagintillion"
+number_names[9774] = "tremilliaducenseptenquinquagintillion"
+number_names[9777] = "tremilliaducenoctoquinquagintillion"
+number_names[9780] = "tremilliaducennovemquinquagintillion"
+number_names[9783] = "tremilliaducensexagintillion"
+number_names[9786] = "tremilliaducenunsexagintillion"
+number_names[9789] = "tremilliaducendosexagintillion"
+number_names[9792] = "tremilliaducentresexagintillion"
+number_names[9795] = "tremilliaducenquattuorsexagintillion"
+number_names[9798] = "tremilliaducenquinsexagintillion"
+number_names[9801] = "tremilliaducensexsexagintillion"
+number_names[9804] = "tremilliaducenseptensexagintillion"
+number_names[9807] = "tremilliaducenoctosexagintillion"
+number_names[9810] = "tremilliaducennovemsexagintillion"
+number_names[9813] = "tremilliaducenseptuagintillion"
+number_names[9816] = "tremilliaducenunseptuagintillion"
+number_names[9819] = "tremilliaducendoseptuagintillion"
+number_names[9822] = "tremilliaducentreseptuagintillion"
+number_names[9825] = "tremilliaducenquattuorseptuagintillion"
+number_names[9828] = "tremilliaducenquinseptuagintillion"
+number_names[9831] = "tremilliaducensexseptuagintillion"
+number_names[9834] = "tremilliaducenseptenseptuagintillion"
+number_names[9837] = "tremilliaducenoctoseptuagintillion"
+number_names[9840] = "tremilliaducennovemseptuagintillion"
+number_names[9843] = "tremilliaducenoctogintillion"
+number_names[9846] = "tremilliaducenunoctogintillion"
+number_names[9849] = "tremilliaducendooctogintillion"
+number_names[9852] = "tremilliaducentreoctogintillion"
+number_names[9855] = "tremilliaducenquattuoroctogintillion"
+number_names[9858] = "tremilliaducenquinoctogintillion"
+number_names[9861] = "tremilliaducensexoctogintillion"
+number_names[9864] = "tremilliaducenseptenoctogintillion"
+number_names[9867] = "tremilliaducenoctooctogintillion"
+number_names[9870] = "tremilliaducennovemoctogintillion"
+number_names[9873] = "tremilliaducennonagintillion"
+number_names[9876] = "tremilliaducenunnonagintillion"
+number_names[9879] = "tremilliaducendononagintillion"
+number_names[9882] = "tremilliaducentrenonagintillion"
+number_names[9885] = "tremilliaducenquattuornonagintillion"
+number_names[9888] = "tremilliaducenquinnonagintillion"
+number_names[9891] = "tremilliaducensexnonagintillion"
+number_names[9894] = "tremilliaducenseptennonagintillion"
+number_names[9897] = "tremilliaducenoctononagintillion"
+number_names[9900] = "tremilliaducennovemnonagintillion"
+number_names[9903] = "tremilliatrecentillion"
+number_names[9906] = "tremilliatrecenuntillion"
+number_names[9909] = "tremilliatrecendotillion"
+number_names[9912] = "tremilliatrecentretillion"
+number_names[9915] = "tremilliatrecenquattuortillion"
+number_names[9918] = "tremilliatrecenquintillion"
+number_names[9921] = "tremilliatrecensextillion"
+number_names[9924] = "tremilliatrecenseptentillion"
+number_names[9927] = "tremilliatrecenoctotillion"
+number_names[9930] = "tremilliatrecennovemtillion"
+number_names[9933] = "tremilliatrecendecillion"
+number_names[9936] = "tremilliatrecenundecillion"
+number_names[9939] = "tremilliatrecendodecillion"
+number_names[9942] = "tremilliatrecentredecillion"
+number_names[9945] = "tremilliatrecenquattuordecillion"
+number_names[9948] = "tremilliatrecenquindecillion"
+number_names[9951] = "tremilliatrecensexdecillion"
+number_names[9954] = "tremilliatrecenseptendecillion"
+number_names[9957] = "tremilliatrecenoctodecillion"
+number_names[9960] = "tremilliatrecennovemdecillion"
+number_names[9963] = "tremilliatrecenvigintillion"
+number_names[9966] = "tremilliatrecenunvigintillion"
+number_names[9969] = "tremilliatrecendovigintillion"
+number_names[9972] = "tremilliatrecentrevigintillion"
+number_names[9975] = "tremilliatrecenquattuorvigintillion"
+number_names[9978] = "tremilliatrecenquinvigintillion"
+number_names[9981] = "tremilliatrecensexvigintillion"
+number_names[9984] = "tremilliatrecenseptenvigintillion"
+number_names[9987] = "tremilliatrecenoctovigintillion"
+number_names[9990] = "tremilliatrecennovemvigintillion"
+number_names[9993] = "tremilliatrecentrigintillion"
+number_names[9996] = "tremilliatrecenuntrigintillion"
+number_names[9999] = "tremilliatrecendotrigintillion"
+
+return number_names
diff --git a/libs/binser.lua b/libs/binser.lua
new file mode 100644
index 0000000..00958f0
--- /dev/null
+++ b/libs/binser.lua
@@ -0,0 +1,750 @@
+-- binser.lua
+
+--[[
+Copyright (c) 2016-2019 Calvin Rose
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+]]
+
+local assert = assert
+local error = error
+local select = select
+local pairs = pairs
+local getmetatable = getmetatable
+local setmetatable = setmetatable
+local type = type
+local loadstring = loadstring or load
+local concat = table.concat
+local char = string.char
+local byte = string.byte
+local format = string.format
+local sub = string.sub
+local dump = string.dump
+local floor = math.floor
+local frexp = math.frexp
+local unpack = unpack or table.unpack
+
+-- Lua 5.3 frexp polyfill
+-- From https://github.com/excessive/cpml/blob/master/modules/utils.lua
+if not frexp then
+ local log, abs, floor = math.log, math.abs, math.floor
+ local log2 = log(2)
+ frexp = function(x)
+ if x == 0 then return 0, 0 end
+ local e = floor(log(abs(x)) / log2 + 1)
+ return x / 2 ^ e, e
+ end
+end
+
+local function pack(...)
+ return {...}, select("#", ...)
+end
+
+local function not_array_index(x, len)
+ return type(x) ~= "number" or x < 1 or x > len or x ~= floor(x)
+end
+
+local function type_check(x, tp, name)
+ assert(type(x) == tp,
+ format("Expected parameter %q to be of type %q.", name, tp))
+end
+
+local bigIntSupport = false
+local isInteger
+if math.type then -- Detect Lua 5.3
+ local mtype = math.type
+ bigIntSupport = loadstring[[
+ local char = string.char
+ return function(n)
+ local nn = n < 0 and -(n + 1) or n
+ local b1 = nn // 0x100000000000000
+ local b2 = nn // 0x1000000000000 % 0x100
+ local b3 = nn // 0x10000000000 % 0x100
+ local b4 = nn // 0x100000000 % 0x100
+ local b5 = nn // 0x1000000 % 0x100
+ local b6 = nn // 0x10000 % 0x100
+ local b7 = nn // 0x100 % 0x100
+ local b8 = nn % 0x100
+ if n < 0 then
+ b1, b2, b3, b4 = 0xFF - b1, 0xFF - b2, 0xFF - b3, 0xFF - b4
+ b5, b6, b7, b8 = 0xFF - b5, 0xFF - b6, 0xFF - b7, 0xFF - b8
+ end
+ return char(212, b1, b2, b3, b4, b5, b6, b7, b8)
+ end]]()
+ isInteger = function(x)
+ return mtype(x) == 'integer'
+ end
+else
+ isInteger = function(x)
+ return floor(x) == x
+ end
+end
+
+-- Copyright (C) 2012-2015 Francois Perrad.
+-- number serialization code modified from https://github.com/fperrad/lua-MessagePack
+-- Encode a number as a big-endian ieee-754 double, big-endian signed 64 bit integer, or a small integer
+local function number_to_str(n)
+ if isInteger(n) then -- int
+ if n <= 100 and n >= -27 then -- 1 byte, 7 bits of data
+ return char(n + 27)
+ elseif n <= 8191 and n >= -8192 then -- 2 bytes, 14 bits of data
+ n = n + 8192
+ return char(128 + (floor(n / 0x100) % 0x100), n % 0x100)
+ elseif bigIntSupport then
+ return bigIntSupport(n)
+ end
+ end
+ local sign = 0
+ if n < 0.0 then
+ sign = 0x80
+ n = -n
+ end
+ local m, e = frexp(n) -- mantissa, exponent
+ if m ~= m then
+ return char(203, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
+ elseif m == 1/0 then
+ if sign == 0 then
+ return char(203, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
+ else
+ return char(203, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
+ end
+ end
+ e = e + 0x3FE
+ if e < 1 then -- denormalized numbers
+ m = m * 2 ^ (52 + e)
+ e = 0
+ else
+ m = (m * 2 - 1) * 2 ^ 52
+ end
+ return char(203,
+ sign + floor(e / 0x10),
+ (e % 0x10) * 0x10 + floor(m / 0x1000000000000),
+ floor(m / 0x10000000000) % 0x100,
+ floor(m / 0x100000000) % 0x100,
+ floor(m / 0x1000000) % 0x100,
+ floor(m / 0x10000) % 0x100,
+ floor(m / 0x100) % 0x100,
+ m % 0x100)
+end
+
+-- Copyright (C) 2012-2015 Francois Perrad.
+-- number deserialization code also modified from https://github.com/fperrad/lua-MessagePack
+local function number_from_str(str, index)
+ local b = byte(str, index)
+ if not b then error("Expected more bytes of input.") end
+ if b < 128 then
+ return b - 27, index + 1
+ elseif b < 192 then
+ local b2 = byte(str, index + 1)
+ if not b2 then error("Expected more bytes of input.") end
+ return b2 + 0x100 * (b - 128) - 8192, index + 2
+ end
+ local b1, b2, b3, b4, b5, b6, b7, b8 = byte(str, index + 1, index + 8)
+ if (not b1) or (not b2) or (not b3) or (not b4) or
+ (not b5) or (not b6) or (not b7) or (not b8) then
+ error("Expected more bytes of input.")
+ end
+ if b == 212 then
+ local flip = b1 >= 128
+ if flip then -- negative
+ b1, b2, b3, b4 = 0xFF - b1, 0xFF - b2, 0xFF - b3, 0xFF - b4
+ b5, b6, b7, b8 = 0xFF - b5, 0xFF - b6, 0xFF - b7, 0xFF - b8
+ end
+ local n = ((((((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4) *
+ 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8
+ if flip then
+ return (-n) - 1, index + 9
+ else
+ return n, index + 9
+ end
+ end
+ if b ~= 203 then
+ error("Expected number")
+ end
+ local sign = b1 > 0x7F and -1 or 1
+ local e = (b1 % 0x80) * 0x10 + floor(b2 / 0x10)
+ local m = ((((((b2 % 0x10) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8
+ local n
+ if e == 0 then
+ if m == 0 then
+ n = sign * 0.0
+ else
+ n = sign * (m / 2 ^ 52) * 2 ^ -1022
+ end
+ elseif e == 0x7FF then
+ if m == 0 then
+ n = sign * (1/0)
+ else
+ n = 0.0/0.0
+ end
+ else
+ n = sign * (1.0 + m / 2 ^ 52) * 2 ^ (e - 0x3FF)
+ end
+ return n, index + 9
+end
+
+
+local function newbinser()
+
+ -- unique table key for getting next value
+ local NEXT = {}
+ local CTORSTACK = {}
+
+ -- NIL = 202
+ -- FLOAT = 203
+ -- TRUE = 204
+ -- FALSE = 205
+ -- STRING = 206
+ -- TABLE = 207
+ -- REFERENCE = 208
+ -- CONSTRUCTOR = 209
+ -- FUNCTION = 210
+ -- RESOURCE = 211
+ -- INT64 = 212
+ -- TABLE WITH META = 213
+
+ local mts = {}
+ local ids = {}
+ local serializers = {}
+ local deserializers = {}
+ local resources = {}
+ local resources_by_name = {}
+ local types = {}
+
+ types["nil"] = function(x, visited, accum)
+ accum[#accum + 1] = "\202"
+ end
+
+ function types.number(x, visited, accum)
+ accum[#accum + 1] = number_to_str(x)
+ end
+
+ function types.boolean(x, visited, accum)
+ accum[#accum + 1] = x and "\204" or "\205"
+ end
+
+ function types.string(x, visited, accum)
+ local alen = #accum
+ if visited[x] then
+ accum[alen + 1] = "\208"
+ accum[alen + 2] = number_to_str(visited[x])
+ else
+ visited[x] = visited[NEXT]
+ visited[NEXT] = visited[NEXT] + 1
+ accum[alen + 1] = "\206"
+ accum[alen + 2] = number_to_str(#x)
+ accum[alen + 3] = x
+ end
+ end
+
+ local function check_custom_type(x, visited, accum)
+ local res = resources[x]
+ if res then
+ accum[#accum + 1] = "\211"
+ types[type(res)](res, visited, accum)
+ return true
+ end
+ local mt = getmetatable(x)
+ local id = mt and ids[mt]
+ if id then
+ local constructing = visited[CTORSTACK]
+ if constructing[x] then
+ error("Infinite loop in constructor.")
+ end
+ constructing[x] = true
+ accum[#accum + 1] = "\209"
+ types[type(id)](id, visited, accum)
+ local args, len = pack(serializers[id](x))
+ accum[#accum + 1] = number_to_str(len)
+ for i = 1, len do
+ local arg = args[i]
+ types[type(arg)](arg, visited, accum)
+ end
+ visited[x] = visited[NEXT]
+ visited[NEXT] = visited[NEXT] + 1
+ -- We finished constructing
+ constructing[x] = nil
+ return true
+ end
+ end
+
+ function types.userdata(x, visited, accum)
+ if visited[x] then
+ accum[#accum + 1] = "\208"
+ accum[#accum + 1] = number_to_str(visited[x])
+ else
+ if check_custom_type(x, visited, accum) then return end
+ error("Cannot serialize this userdata.")
+ end
+ end
+
+ function types.table(x, visited, accum)
+ if visited[x] then
+ accum[#accum + 1] = "\208"
+ accum[#accum + 1] = number_to_str(visited[x])
+ else
+ if check_custom_type(x, visited, accum) then return end
+ visited[x] = visited[NEXT]
+ visited[NEXT] = visited[NEXT] + 1
+ local xlen = #x
+ local mt = getmetatable(x)
+ if mt then
+ accum[#accum + 1] = "\213"
+ types.table(mt, visited, accum)
+ else
+ accum[#accum + 1] = "\207"
+ end
+ accum[#accum + 1] = number_to_str(xlen)
+ for i = 1, xlen do
+ local v = x[i]
+ types[type(v)](v, visited, accum)
+ end
+ local key_count = 0
+ for k in pairs(x) do
+ if not_array_index(k, xlen) then
+ key_count = key_count + 1
+ end
+ end
+ accum[#accum + 1] = number_to_str(key_count)
+ for k, v in pairs(x) do
+ if not_array_index(k, xlen) then
+ types[type(k)](k, visited, accum)
+ types[type(v)](v, visited, accum)
+ end
+ end
+ end
+ end
+
+ types["function"] = function(x, visited, accum)
+ if visited[x] then
+ accum[#accum + 1] = "\208"
+ accum[#accum + 1] = number_to_str(visited[x])
+ else
+ if check_custom_type(x, visited, accum) then return end
+ visited[x] = visited[NEXT]
+ visited[NEXT] = visited[NEXT] + 1
+ local str = dump(x)
+ accum[#accum + 1] = "\210"
+ accum[#accum + 1] = number_to_str(#str)
+ accum[#accum + 1] = str
+ end
+ end
+
+ types.cdata = function(x, visited, accum)
+ if visited[x] then
+ accum[#accum + 1] = "\208"
+ accum[#accum + 1] = number_to_str(visited[x])
+ else
+ if check_custom_type(x, visited, #accum) then return end
+ error("Cannot serialize this cdata.")
+ end
+ end
+
+ types.thread = function() error("Cannot serialize threads.") end
+
+ local function deserialize_value(str, index, visited)
+ local t = byte(str, index)
+ if not t then return nil, index end
+ if t < 128 then
+ return t - 27, index + 1
+ elseif t < 192 then
+ local b2 = byte(str, index + 1)
+ if not b2 then error("Expected more bytes of input.") end
+ return b2 + 0x100 * (t - 128) - 8192, index + 2
+ elseif t == 202 then
+ return nil, index + 1
+ elseif t == 203 or t == 212 then
+ return number_from_str(str, index)
+ elseif t == 204 then
+ return true, index + 1
+ elseif t == 205 then
+ return false, index + 1
+ elseif t == 206 then
+ local length, dataindex = number_from_str(str, index + 1)
+ local nextindex = dataindex + length
+ if not (length >= 0) then error("Bad string length") end
+ if #str < nextindex - 1 then error("Expected more bytes of string") end
+ local substr = sub(str, dataindex, nextindex - 1)
+ visited[#visited + 1] = substr
+ return substr, nextindex
+ elseif t == 207 or t == 213 then
+ local mt, count, nextindex
+ local ret = {}
+ visited[#visited + 1] = ret
+ nextindex = index + 1
+ if t == 213 then
+ mt, nextindex = deserialize_value(str, nextindex, visited)
+ if type(mt) ~= "table" then error("Expected table metatable") end
+ end
+ count, nextindex = number_from_str(str, nextindex)
+ for i = 1, count do
+ local oldindex = nextindex
+ ret[i], nextindex = deserialize_value(str, nextindex, visited)
+ if nextindex == oldindex then error("Expected more bytes of input.") end
+ end
+ count, nextindex = number_from_str(str, nextindex)
+ for i = 1, count do
+ local k, v
+ local oldindex = nextindex
+ k, nextindex = deserialize_value(str, nextindex, visited)
+ if nextindex == oldindex then error("Expected more bytes of input.") end
+ oldindex = nextindex
+ v, nextindex = deserialize_value(str, nextindex, visited)
+ if nextindex == oldindex then error("Expected more bytes of input.") end
+ if k == nil then error("Can't have nil table keys") end
+ ret[k] = v
+ end
+ if mt then setmetatable(ret, mt) end
+ return ret, nextindex
+ elseif t == 208 then
+ local ref, nextindex = number_from_str(str, index + 1)
+ return visited[ref], nextindex
+ elseif t == 209 then
+ local count
+ local name, nextindex = deserialize_value(str, index + 1, visited)
+ count, nextindex = number_from_str(str, nextindex)
+ local args = {}
+ for i = 1, count do
+ local oldindex = nextindex
+ args[i], nextindex = deserialize_value(str, nextindex, visited)
+ if nextindex == oldindex then error("Expected more bytes of input.") end
+ end
+ if not name or not deserializers[name] then
+ error(("Cannot deserialize class '%s'"):format(tostring(name)))
+ end
+ local ret = deserializers[name](unpack(args))
+ visited[#visited + 1] = ret
+ return ret, nextindex
+ elseif t == 210 then
+ local length, dataindex = number_from_str(str, index + 1)
+ local nextindex = dataindex + length
+ if not (length >= 0) then error("Bad string length") end
+ if #str < nextindex - 1 then error("Expected more bytes of string") end
+ local ret = loadstring(sub(str, dataindex, nextindex - 1))
+ visited[#visited + 1] = ret
+ return ret, nextindex
+ elseif t == 211 then
+ local resname, nextindex = deserialize_value(str, index + 1, visited)
+ if resname == nil then error("Got nil resource name") end
+ local res = resources_by_name[resname]
+ if res == nil then
+ error(("No resources found for name '%s'"):format(tostring(resname)))
+ end
+ return res, nextindex
+ else
+ error("Could not deserialize type byte " .. t .. ".")
+ end
+ end
+
+ local function serialize(...)
+ local visited = {[NEXT] = 1, [CTORSTACK] = {}}
+ local accum = {}
+ for i = 1, select("#", ...) do
+ local x = select(i, ...)
+ types[type(x)](x, visited, accum)
+ end
+ return concat(accum)
+ end
+
+ local function make_file_writer(file)
+ return setmetatable({}, {
+ __newindex = function(_, _, v)
+ file:write(v)
+ end
+ })
+ end
+
+ local function serialize_to_file(path, mode, ...)
+ local file, err = io.open(path, mode)
+ assert(file, err)
+ local visited = {[NEXT] = 1, [CTORSTACK] = {}}
+ local accum = make_file_writer(file)
+ for i = 1, select("#", ...) do
+ local x = select(i, ...)
+ types[type(x)](x, visited, accum)
+ end
+ -- flush the writer
+ file:flush()
+ file:close()
+ end
+
+ local function writeFile(path, ...)
+ return serialize_to_file(path, "wb", ...)
+ end
+
+ local function appendFile(path, ...)
+ return serialize_to_file(path, "ab", ...)
+ end
+
+ local function deserialize(str, index)
+ assert(type(str) == "string", "Expected string to deserialize.")
+ local vals = {}
+ index = index or 1
+ local visited = {}
+ local len = 0
+ local val
+ while true do
+ local nextindex
+ val, nextindex = deserialize_value(str, index, visited)
+ if nextindex > index then
+ len = len + 1
+ vals[len] = val
+ index = nextindex
+ else
+ break
+ end
+ end
+ return vals, len
+ end
+
+ local function deserializeN(str, n, index)
+ assert(type(str) == "string", "Expected string to deserialize.")
+ n = n or 1
+ assert(type(n) == "number", "Expected a number for parameter n.")
+ assert(n > 0 and floor(n) == n, "N must be a poitive integer.")
+ local vals = {}
+ index = index or 1
+ local visited = {}
+ local len = 0
+ local val
+ while len < n do
+ local nextindex
+ val, nextindex = deserialize_value(str, index, visited)
+ if nextindex > index then
+ len = len + 1
+ vals[len] = val
+ index = nextindex
+ else
+ break
+ end
+ end
+ vals[len + 1] = index
+ return unpack(vals, 1, n + 1)
+ end
+
+ local function readFile(path)
+ local file, err = io.open(path, "rb")
+ assert(file, err)
+ local str = file:read("*all")
+ file:close()
+ return deserialize(str)
+ end
+
+ -- Resources
+
+ local function registerResource(resource, name)
+ type_check(name, "string", "name")
+ assert(not resources[resource],
+ "Resource already registered.")
+ assert(not resources_by_name[name],
+ format("Resource %q already exists.", name))
+ resources_by_name[name] = resource
+ resources[resource] = name
+ return resource
+ end
+
+ local function unregisterResource(name)
+ type_check(name, "string", "name")
+ assert(resources_by_name[name], format("Resource %q does not exist.", name))
+ local resource = resources_by_name[name]
+ resources_by_name[name] = nil
+ resources[resource] = nil
+ return resource
+ end
+
+ -- Templating
+
+ local function normalize_template(template)
+ local ret = {}
+ for i = 1, #template do
+ ret[i] = template[i]
+ end
+ local non_array_part = {}
+ -- The non-array part of the template (nested templates) have to be deterministic, so they are sorted.
+ -- This means that inherently non deterministicly sortable keys (tables, functions) should NOT be used
+ -- in templates. Looking for way around this.
+ for k in pairs(template) do
+ if not_array_index(k, #template) then
+ non_array_part[#non_array_part + 1] = k
+ end
+ end
+ table.sort(non_array_part)
+ for i = 1, #non_array_part do
+ local name = non_array_part[i]
+ ret[#ret + 1] = {name, normalize_template(template[name])}
+ end
+ return ret
+ end
+
+ local function templatepart_serialize(part, argaccum, x, len)
+ local extras = {}
+ local extracount = 0
+ for k, v in pairs(x) do
+ extras[k] = v
+ extracount = extracount + 1
+ end
+ for i = 1, #part do
+ local name
+ if type(part[i]) == "table" then
+ name = part[i][1]
+ len = templatepart_serialize(part[i][2], argaccum, x[name], len)
+ else
+ name = part[i]
+ len = len + 1
+ argaccum[len] = x[part[i]]
+ end
+ if extras[name] ~= nil then
+ extracount = extracount - 1
+ extras[name] = nil
+ end
+ end
+ if extracount > 0 then
+ argaccum[len + 1] = extras
+ else
+ argaccum[len + 1] = nil
+ end
+ return len + 1
+ end
+
+ local function templatepart_deserialize(ret, part, values, vindex)
+ for i = 1, #part do
+ local name = part[i]
+ if type(name) == "table" then
+ local newret = {}
+ ret[name[1]] = newret
+ vindex = templatepart_deserialize(newret, name[2], values, vindex)
+ else
+ ret[name] = values[vindex]
+ vindex = vindex + 1
+ end
+ end
+ local extras = values[vindex]
+ if extras then
+ for k, v in pairs(extras) do
+ ret[k] = v
+ end
+ end
+ return vindex + 1
+ end
+
+ local function template_serializer_and_deserializer(metatable, template)
+ return function(x)
+ local argaccum = {}
+ local len = templatepart_serialize(template, argaccum, x, 0)
+ return unpack(argaccum, 1, len)
+ end, function(...)
+ local ret = {}
+ local args = {...}
+ templatepart_deserialize(ret, template, args, 1)
+ return setmetatable(ret, metatable)
+ end
+ end
+
+ -- Used to serialize classes withh custom serializers and deserializers.
+ -- If no _serialize or _deserialize (or no _template) value is found in the
+ -- metatable, then the metatable is registered as a resources.
+ local function register(metatable, name, serialize, deserialize)
+ if type(metatable) == "table" then
+ name = name or metatable.name
+ serialize = serialize or metatable._serialize
+ deserialize = deserialize or metatable._deserialize
+ if (not serialize) or (not deserialize) then
+ if metatable._template then
+ -- Register as template
+ local t = normalize_template(metatable._template)
+ serialize, deserialize = template_serializer_and_deserializer(metatable, t)
+ else
+ -- Register the metatable as a resource. This is semantically
+ -- similar and more flexible (handles cycles).
+ registerResource(metatable, name)
+ return
+ end
+ end
+ elseif type(metatable) == "string" then
+ name = name or metatable
+ end
+ type_check(name, "string", "name")
+ type_check(serialize, "function", "serialize")
+ type_check(deserialize, "function", "deserialize")
+ assert((not ids[metatable]) and (not resources[metatable]),
+ "Metatable already registered.")
+ assert((not mts[name]) and (not resources_by_name[name]),
+ ("Name %q already registered."):format(name))
+ mts[name] = metatable
+ ids[metatable] = name
+ serializers[name] = serialize
+ deserializers[name] = deserialize
+ return metatable
+ end
+
+ local function unregister(item)
+ local name, metatable
+ if type(item) == "string" then -- assume name
+ name, metatable = item, mts[item]
+ else -- assume metatable
+ name, metatable = ids[item], item
+ end
+ type_check(name, "string", "name")
+ mts[name] = nil
+ if (metatable) then
+ resources[metatable] = nil
+ ids[metatable] = nil
+ end
+ serializers[name] = nil
+ deserializers[name] = nil
+ resources_by_name[name] = nil;
+ return metatable
+ end
+
+ local function registerClass(class, name)
+ name = name or class.name
+ if class.__instanceDict then -- middleclass
+ register(class.__instanceDict, name)
+ else -- assume 30log or similar library
+ register(class, name)
+ end
+ return class
+ end
+
+ return {
+ VERSION = "0.0-8",
+ -- aliases
+ s = serialize,
+ d = deserialize,
+ dn = deserializeN,
+ r = readFile,
+ w = writeFile,
+ a = appendFile,
+
+ serialize = serialize,
+ deserialize = deserialize,
+ deserializeN = deserializeN,
+ readFile = readFile,
+ writeFile = writeFile,
+ appendFile = appendFile,
+ register = register,
+ unregister = unregister,
+ registerResource = registerResource,
+ unregisterResource = unregisterResource,
+ registerClass = registerClass,
+
+ newbinser = newbinser
+ }
+end
+
+return newbinser()
diff --git a/libs/bitser.lua b/libs/bitser.lua
new file mode 100644
index 0000000..b76a9d7
--- /dev/null
+++ b/libs/bitser.lua
@@ -0,0 +1,496 @@
+--[[
+Copyright (c) 2020, Jasmijn Wellner
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+]]
+
+local VERSION = '1.1'
+
+local floor = math.floor
+local pairs = pairs
+local type = type
+local insert = table.insert
+local getmetatable = getmetatable
+local setmetatable = setmetatable
+
+local ffi = require("ffi")
+local buf_pos = 0
+local buf_size = -1
+local buf = nil
+local buf_is_writable = true
+local writable_buf = nil
+local writable_buf_size = nil
+local includeMetatables = true -- togglable with bitser.includeMetatables(false)
+local SEEN_LEN = {}
+
+local function Buffer_prereserve(min_size)
+ if buf_size < min_size then
+ buf_size = min_size
+ buf = ffi.new("uint8_t[?]", buf_size)
+ buf_is_writable = true
+ end
+end
+
+local function Buffer_clear()
+ buf_size = -1
+ buf = nil
+ buf_is_writable = true
+ writable_buf = nil
+ writable_buf_size = nil
+end
+
+local function Buffer_makeBuffer(size)
+ if not buf_is_writable then
+ buf = writable_buf
+ buf_size = writable_buf_size
+ writable_buf = nil
+ writable_buf_size = nil
+ buf_is_writable = true
+ end
+ buf_pos = 0
+ Buffer_prereserve(size)
+end
+
+local function Buffer_newReader(str)
+ Buffer_makeBuffer(#str)
+ ffi.copy(buf, str, #str)
+end
+
+local function Buffer_newDataReader(data, size)
+ if buf_is_writable then
+ writable_buf = buf
+ writable_buf_size = buf_size
+ end
+ buf_is_writable = false
+ buf_pos = 0
+ buf_size = size
+ buf = ffi.cast("uint8_t*", data)
+end
+
+local function Buffer_reserve(additional_size)
+ while buf_pos + additional_size > buf_size do
+ buf_size = buf_size * 2
+ local oldbuf = buf
+ buf = ffi.new("uint8_t[?]", buf_size)
+ buf_is_writable = true
+ ffi.copy(buf, oldbuf, buf_pos)
+ end
+end
+
+local function Buffer_write_byte(x)
+ Buffer_reserve(1)
+ buf[buf_pos] = x
+ buf_pos = buf_pos + 1
+end
+
+local function Buffer_write_raw(data, len)
+ Buffer_reserve(len)
+ ffi.copy(buf + buf_pos, data, len)
+ buf_pos = buf_pos + len
+end
+
+local function Buffer_write_string(s)
+ Buffer_write_raw(s, #s)
+end
+
+local function Buffer_write_data(ct, len, ...)
+ Buffer_write_raw(ffi.new(ct, ...), len)
+end
+
+local function Buffer_ensure(numbytes)
+ if buf_pos + numbytes > buf_size then
+ error("malformed serialized data")
+ end
+end
+
+local function Buffer_read_byte()
+ Buffer_ensure(1)
+ local x = buf[buf_pos]
+ buf_pos = buf_pos + 1
+ return x
+end
+
+local function Buffer_read_string(len)
+ Buffer_ensure(len)
+ local x = ffi.string(buf + buf_pos, len)
+ buf_pos = buf_pos + len
+ return x
+end
+
+local function Buffer_read_raw(data, len)
+ ffi.copy(data, buf + buf_pos, len)
+ buf_pos = buf_pos + len
+ return data
+end
+
+local function Buffer_read_data(ct, len)
+ return Buffer_read_raw(ffi.new(ct), len)
+end
+
+local resource_registry = {}
+local resource_name_registry = {}
+local class_registry = {}
+local class_name_registry = {}
+local classkey_registry = {}
+local class_deserialize_registry = {}
+
+local serialize_value
+
+local function write_number(value, _)
+ if floor(value) == value and value >= -2147483648 and value <= 2147483647 then
+ if value >= -27 and value <= 100 then
+ --small int
+ Buffer_write_byte(value + 27)
+ elseif value >= -32768 and value <= 32767 then
+ --short int
+ Buffer_write_byte(250)
+ Buffer_write_data("int16_t[1]", 2, value)
+ else
+ --long int
+ Buffer_write_byte(245)
+ Buffer_write_data("int32_t[1]", 4, value)
+ end
+ else
+ --double
+ Buffer_write_byte(246)
+ Buffer_write_data("double[1]", 8, value)
+ end
+end
+
+local function write_string(value, _)
+ if #value < 32 then
+ --short string
+ Buffer_write_byte(192 + #value)
+ else
+ --long string
+ Buffer_write_byte(244)
+ write_number(#value)
+ end
+ Buffer_write_string(value)
+end
+
+local function write_nil(_, _)
+ Buffer_write_byte(247)
+end
+
+local function write_boolean(value, _)
+ Buffer_write_byte(value and 249 or 248)
+end
+
+local function write_table(value, seen)
+ local classkey
+ local metatable = getmetatable(value)
+ local classname = (class_name_registry[value.class] -- MiddleClass
+ or class_name_registry[value.__baseclass] -- SECL
+ or class_name_registry[metatable] -- hump.class
+ or class_name_registry[value.__class__] -- Slither
+ or class_name_registry[value.__class]) -- Moonscript class
+ if classname then
+ classkey = classkey_registry[classname]
+ Buffer_write_byte(242)
+ serialize_value(classname, seen)
+ elseif includeMetatables and metatable then
+ Buffer_write_byte(253)
+ else
+ Buffer_write_byte(240)
+ end
+ local len = #value
+ write_number(len, seen)
+ for i = 1, len do
+ serialize_value(value[i], seen)
+ end
+ local klen = 0
+ for k in pairs(value) do
+ if (type(k) ~= 'number' or floor(k) ~= k or k > len or k < 1) and k ~= classkey then
+ klen = klen + 1
+ end
+ end
+ write_number(klen, seen)
+ for k, v in pairs(value) do
+ if (type(k) ~= 'number' or floor(k) ~= k or k > len or k < 1) and k ~= classkey then
+ serialize_value(k, seen)
+ serialize_value(v, seen)
+ end
+ end
+ if includeMetatables and metatable and not classname then
+ serialize_value(metatable, seen)
+ end
+end
+
+local function write_cdata(value, seen)
+ local ty = ffi.typeof(value)
+ if ty == value then
+ -- ctype
+ Buffer_write_byte(251)
+ serialize_value(tostring(ty):sub(7, -2), seen)
+ return
+ end
+ -- cdata
+ Buffer_write_byte(252)
+ serialize_value(ty, seen)
+ local len = ffi.sizeof(value)
+ write_number(len)
+ Buffer_write_raw(ffi.typeof('$[1]', ty)(value), len)
+end
+
+local types = {number = write_number, string = write_string, table = write_table, boolean = write_boolean, ["nil"] = write_nil, cdata = write_cdata}
+
+serialize_value = function(value, seen)
+ if seen[value] then
+ local ref = seen[value]
+ if ref < 64 then
+ --small reference
+ Buffer_write_byte(128 + ref)
+ else
+ --long reference
+ Buffer_write_byte(243)
+ write_number(ref, seen)
+ end
+ return
+ end
+ local t = type(value)
+ if t ~= 'number' and t ~= 'boolean' and t ~= 'nil' and t ~= 'cdata' then
+ seen[value] = seen[SEEN_LEN]
+ seen[SEEN_LEN] = seen[SEEN_LEN] + 1
+ end
+ if resource_name_registry[value] then
+ local name = resource_name_registry[value]
+ if #name < 16 then
+ --small resource
+ Buffer_write_byte(224 + #name)
+ Buffer_write_string(name)
+ else
+ --long resource
+ Buffer_write_byte(241)
+ write_string(name, seen)
+ end
+ return
+ end
+ (types[t] or
+ error("cannot serialize type " .. t)
+ )(value, seen)
+end
+
+local function serialize(value)
+ Buffer_makeBuffer(4096)
+ local seen = {[SEEN_LEN] = 0}
+ serialize_value(value, seen)
+end
+
+local function add_to_seen(value, seen)
+ insert(seen, value)
+ return value
+end
+
+local function reserve_seen(seen)
+ insert(seen, 42)
+ return #seen
+end
+
+local function deserialize_value(seen)
+ local t = Buffer_read_byte()
+ if t < 128 then
+ --small int
+ return t - 27
+ elseif t < 192 then
+ --small reference
+ return seen[t - 127]
+ elseif t < 224 then
+ --small string
+ return add_to_seen(Buffer_read_string(t - 192), seen)
+ elseif t < 240 then
+ --small resource
+ return add_to_seen(resource_registry[Buffer_read_string(t - 224)], seen)
+ elseif t == 240 or t == 253 then
+ --table
+ local v = add_to_seen({}, seen)
+ local len = deserialize_value(seen)
+ for i = 1, len do
+ v[i] = deserialize_value(seen)
+ end
+ len = deserialize_value(seen)
+ for _ = 1, len do
+ local key = deserialize_value(seen)
+ v[key] = deserialize_value(seen)
+ end
+ if t == 253 then
+ if includeMetatables then
+ setmetatable(v, deserialize_value(seen))
+ end
+ end
+ return v
+ elseif t == 241 then
+ --long resource
+ local idx = reserve_seen(seen)
+ local value = resource_registry[deserialize_value(seen)]
+ seen[idx] = value
+ return value
+ elseif t == 242 then
+ --instance
+ local instance = add_to_seen({}, seen)
+ local classname = deserialize_value(seen)
+ local class = class_registry[classname]
+ local classkey = classkey_registry[classname]
+ local deserializer = class_deserialize_registry[classname]
+ local len = deserialize_value(seen)
+ for i = 1, len do
+ instance[i] = deserialize_value(seen)
+ end
+ len = deserialize_value(seen)
+ for _ = 1, len do
+ local key = deserialize_value(seen)
+ instance[key] = deserialize_value(seen)
+ end
+ if classkey then
+ instance[classkey] = class
+ end
+ return deserializer(instance, class)
+ elseif t == 243 then
+ --reference
+ return seen[deserialize_value(seen) + 1]
+ elseif t == 244 then
+ --long string
+ return add_to_seen(Buffer_read_string(deserialize_value(seen)), seen)
+ elseif t == 245 then
+ --long int
+ return Buffer_read_data("int32_t[1]", 4)[0]
+ elseif t == 246 then
+ --double
+ return Buffer_read_data("double[1]", 8)[0]
+ elseif t == 247 then
+ --nil
+ return nil
+ elseif t == 248 then
+ --false
+ return false
+ elseif t == 249 then
+ --true
+ return true
+ elseif t == 250 then
+ --short int
+ return Buffer_read_data("int16_t[1]", 2)[0]
+ elseif t == 251 then
+ --ctype
+ return ffi.typeof(deserialize_value(seen))
+ elseif t == 252 then
+ local ctype = deserialize_value(seen)
+ local len = deserialize_value(seen)
+ local read_into = ffi.typeof('$[1]', ctype)()
+ Buffer_read_raw(read_into, len)
+ return ctype(read_into[0])
+ else
+ error("unsupported serialized type " .. t)
+ end
+end
+
+local function deserialize_MiddleClass(instance, class)
+ return setmetatable(instance, class.__instanceDict)
+end
+
+local function deserialize_SECL(instance, class)
+ return setmetatable(instance, getmetatable(class))
+end
+
+local deserialize_humpclass = setmetatable
+
+local function deserialize_Slither(instance, class)
+ return getmetatable(class).allocate(instance)
+end
+
+local function deserialize_Moonscript(instance, class)
+ return setmetatable(instance, class.__base)
+end
+
+return {dumps = function(value)
+ serialize(value)
+ return ffi.string(buf, buf_pos)
+end, dumpLoveFile = function(fname, value)
+ serialize(value)
+ assert(love.filesystem.write(fname, ffi.string(buf, buf_pos)))
+end, loadLoveFile = function(fname)
+ local serializedData, error = love.filesystem.newFileData(fname)
+ assert(serializedData, error)
+ Buffer_newDataReader(serializedData:getPointer(), serializedData:getSize())
+ local value = deserialize_value({})
+ -- serializedData needs to not be collected early in a tail-call
+ -- so make sure deserialize_value returns before loadLoveFile does
+ return value
+end, loadData = function(data, size)
+ if size == 0 then
+ error('cannot load value from empty data')
+ end
+ Buffer_newDataReader(data, size)
+ return deserialize_value({})
+end, loads = function(str)
+ if #str == 0 then
+ error('cannot load value from empty string')
+ end
+ Buffer_newReader(str)
+ return deserialize_value({})
+end, includeMetatables = function(bool)
+ includeMetatables = not not bool
+end, register = function(name, resource)
+ assert(not resource_registry[name], name .. " already registered")
+ resource_registry[name] = resource
+ resource_name_registry[resource] = name
+ return resource
+end, unregister = function(name)
+ resource_name_registry[resource_registry[name]] = nil
+ resource_registry[name] = nil
+end, registerClass = function(name, class, classkey, deserializer)
+ if not class then
+ class = name
+ name = class.__name__ or class.name or class.__name
+ end
+ if not classkey then
+ if class.__instanceDict then
+ -- assume MiddleClass
+ classkey = 'class'
+ elseif class.__baseclass then
+ -- assume SECL
+ classkey = '__baseclass'
+ end
+ -- assume hump.class, Slither, Moonscript class or something else that doesn't store the
+ -- class directly on the instance
+ end
+ if not deserializer then
+ if class.__instanceDict then
+ -- assume MiddleClass
+ deserializer = deserialize_MiddleClass
+ elseif class.__baseclass then
+ -- assume SECL
+ deserializer = deserialize_SECL
+ elseif class.__index == class then
+ -- assume hump.class
+ deserializer = deserialize_humpclass
+ elseif class.__name__ then
+ -- assume Slither
+ deserializer = deserialize_Slither
+ elseif class.__base then
+ -- assume Moonscript class
+ deserializer = deserialize_Moonscript
+ else
+ error("no deserializer given for unsupported class library")
+ end
+ end
+ class_registry[name] = class
+ classkey_registry[name] = classkey
+ class_deserialize_registry[name] = deserializer
+ class_name_registry[class] = name
+ return class
+end, unregisterClass = function(name)
+ class_name_registry[class_registry[name]] = nil
+ classkey_registry[name] = nil
+ class_deserialize_registry[name] = nil
+ class_registry[name] = nil
+end, reserveBuffer = Buffer_prereserve, clearBuffer = Buffer_clear, version = VERSION}
diff --git a/libs/classic.lua b/libs/classic.lua
new file mode 100644
index 0000000..5c4f8e8
--- /dev/null
+++ b/libs/classic.lua
@@ -0,0 +1,61 @@
+--
+-- classic
+--
+-- Copyright (c) 2014, rxi
+--
+-- This module is free software; you can redistribute it and/or modify it under
+-- the terms of the MIT license. See LICENSE for details.
+--
+
+
+local Object = {}
+Object.__index = Object
+
+function Object:new()
+end
+
+function Object:extend()
+ local cls = {}
+ for k, v in pairs(self) do
+ if k:find("__") == 1 then
+ cls[k] = v
+ end
+ end
+ cls.__index = cls
+ cls.super = self
+ setmetatable(cls, self)
+ return cls
+end
+
+function Object:implement(...)
+ for _, cls in pairs({...}) do
+ for k, v in pairs(cls) do
+ if self[k] == nil and type(v) == "function" then
+ self[k] = v
+ end
+ end
+ end
+end
+
+function Object:is(T)
+ local mt = getmetatable(self)
+ while mt do
+ if mt == T then
+ return true
+ end
+ mt = getmetatable(mt)
+ end
+ return false
+end
+
+function Object:__tostring()
+ return "Object"
+end
+
+function Object:__call(...)
+ local obj = setmetatable({}, self)
+ obj:new(...)
+ return obj
+end
+
+return Object
\ No newline at end of file
diff --git a/libs/lualzw.lua b/libs/lualzw.lua
new file mode 100644
index 0000000..e8f00d6
--- /dev/null
+++ b/libs/lualzw.lua
@@ -0,0 +1,165 @@
+--[[
+MIT License
+
+Copyright (c) 2016 Rochet2
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+]]
+
+local char = string.char
+local type = type
+local select = select
+local sub = string.sub
+local tconcat = table.concat
+
+local basedictcompress = {}
+local basedictdecompress = {}
+for i = 0, 255 do
+ local ic, iic = char(i), char(i, 0)
+ basedictcompress[ic] = iic
+ basedictdecompress[iic] = ic
+end
+
+local function dictAddA(str, dict, a, b)
+ if a >= 256 then
+ a, b = 0, b+1
+ if b >= 256 then
+ dict = {}
+ b = 1
+ end
+ end
+ dict[str] = char(a,b)
+ a = a+1
+ return dict, a, b
+end
+
+local function compress(input)
+ if type(input) ~= "string" then
+ return nil, "string expected, got "..type(input)
+ end
+ local len = #input
+ if len <= 1 then
+ return "u"..input
+ end
+
+ local dict = {}
+ local a, b = 0, 1
+
+ local result = {"c"}
+ local resultlen = 1
+ local n = 2
+ local word = ""
+ for i = 1, len do
+ local c = sub(input, i, i)
+ local wc = word..c
+ if not (basedictcompress[wc] or dict[wc]) then
+ local write = basedictcompress[word] or dict[word]
+ if not write then
+ return nil, "algorithm error, could not fetch word"
+ end
+ result[n] = write
+ resultlen = resultlen + #write
+ n = n+1
+ if len <= resultlen then
+ return "u"..input
+ end
+ dict, a, b = dictAddA(wc, dict, a, b)
+ word = c
+ else
+ word = wc
+ end
+ end
+ result[n] = basedictcompress[word] or dict[word]
+ resultlen = resultlen+#result[n]
+ n = n+1
+ if len <= resultlen then
+ return "u"..input
+ end
+ return tconcat(result)
+end
+
+local function dictAddB(str, dict, a, b)
+ if a >= 256 then
+ a, b = 0, b+1
+ if b >= 256 then
+ dict = {}
+ b = 1
+ end
+ end
+ dict[char(a,b)] = str
+ a = a+1
+ return dict, a, b
+end
+
+local function decompress(input)
+ if type(input) ~= "string" then
+ return nil, "string expected, got "..type(input)
+ end
+
+ if #input < 1 then
+ return nil, "invalid input - not a compressed string"
+ end
+
+ local control = sub(input, 1, 1)
+ if control == "u" then
+ return sub(input, 2)
+ elseif control ~= "c" then
+ return nil, "invalid input - not a compressed string"
+ end
+ input = sub(input, 2)
+ local len = #input
+
+ if len < 2 then
+ return nil, "invalid input - not a compressed string"
+ end
+
+ local dict = {}
+ local a, b = 0, 1
+
+ local result = {}
+ local n = 1
+ local last = sub(input, 1, 2)
+ result[n] = basedictdecompress[last] or dict[last]
+ n = n+1
+ for i = 3, len, 2 do
+ local code = sub(input, i, i+1)
+ local lastStr = basedictdecompress[last] or dict[last]
+ if not lastStr then
+ return nil, "could not find last from dict. Invalid input?"
+ end
+ local toAdd = basedictdecompress[code] or dict[code]
+ if toAdd then
+ result[n] = toAdd
+ n = n+1
+ dict, a, b = dictAddB(lastStr..sub(toAdd, 1, 1), dict, a, b)
+ else
+ local tmp = lastStr..sub(lastStr, 1, 1)
+ result[n] = tmp
+ n = n+1
+ dict, a, b = dictAddB(tmp, dict, a, b)
+ end
+ last = code
+ end
+ return tconcat(result)
+end
+
+return {
+ compress = compress,
+ decompress = decompress,
+}
diff --git a/libs/simple-button.lua b/libs/simple-button.lua
new file mode 100644
index 0000000..b1667a7
--- /dev/null
+++ b/libs/simple-button.lua
@@ -0,0 +1,314 @@
+-- SIMPLE-BUTTON.lua
+-- A simple module that aims to help you quickly create buttons
+-- It is can be used as a base class to help you quickly creating button
+-- This module has type notations so IntelliSense should give you some suggestions
+local BUTTON = {}
+
+-- MIT License
+
+-- Copyright (c) 2024 SweetSea-ButImNotSweet
+
+-- Permission is hereby granted, free of charge, to any person obtaining a copy
+-- of this software and associated documentation files (the "Software"), to deal
+-- in the Software without restriction, including without limitation the rights
+-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-- copies of the Software, and to permit persons to whom the Software is
+-- furnished to do so, subject to the following conditions:
+
+-- The above copyright notice and this permission notice shall be included in all
+-- copies or substantial portions of the Software.
+
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+-- SOFTWARE.
+
+local NULL = function() end
+local function checkColorTableValidation(C)
+ if C and type(C) == "table" and (#C == 3 or #C == 4) then
+ for _, v in pairs(C) do
+ if type(v) ~= "number" or v<0 or v>1 then return false end
+ end
+ else
+ return false
+ end
+ return true
+end
+
+---@class BUTTON.button
+---@field text? string|function # Name of the button, will be used to show
+---@field textOrientation? "center"|"justify"|"left"|"right"
+---
+---@field x? number # Position of the button (x, y, w, h)
+---@field y? number # Position of the button (x, y, w, h)
+---@field w? number # Position of the button (x, y, w, h)
+---@field h? number # Position of the button (x, y, w, h)
+---@field r? number # Radius corner, cannot larger than half of button's width and half of button's height
+---
+---@field borderWidth? number|1 # Line width will be used to draw button
+---@field borderJoin? "bevel"|"miter"|"none"
+---@field borderStyle? "rough"|"smooth"
+---
+---@field font? love.Font
+---
+---@field backgroundColor? integer[]
+---@field textColor? integer[]
+---@field borderColor? integer[]
+---@field hoverColor? integer[]
+---@field pressColor? integer[]
+---
+---@field codeWhenPressed? function| # Code will be execute when pressed
+---@field codeWhenReleased? function| # Code will be execute when released
+---@field drawingButtonFunc? function| # The function is used to draw text
You can override the default one if you feel the default text drawing function is not suitable for you
+---
+---@field draw? function
+---@field update? function
+local button = {
+ textOrientation = "center",
+ r = 0,
+
+ backgroundColor = {0,0,0,0},
+ hoverColor = {1,1,1,0.5},
+ pressColor = {0, 1, 0.5, 0.5},
+ borderColor = {1,1,1},
+ textColor = {1,1,1},
+
+ font = love.graphics.newFont(15),
+
+ borderWidth = 1,
+ borderJoin = "none",
+ borderStyle = "smooth",
+
+ codeWhenPressed = NULL,
+ codeWhenReleased = NULL,
+ update = NULL,
+
+ _hovering = false,
+ _pressed = false,
+ _touchID = false,
+}; button.__index = button
+function button:draw()
+ love.graphics.setLineWidth(self.borderWidth)
+ love.graphics.setLineStyle(self.borderStyle)
+ love.graphics.setLineJoin(self.borderJoin)
+
+ love.graphics.setColor(self.backgroundColor)
+ love.graphics.rectangle('fill', self.x, self.y, self.w, self.h, self.r)
+
+ if self._pressed then
+ love.graphics.setColor(self.pressColor)
+ love.graphics.rectangle('fill', self.x, self.y, self.w, self.h, self.r)
+ elseif self._hovering then
+ love.graphics.setColor(self.hoverColor)
+ love.graphics.rectangle('fill', self.x, self.y, self.w, self.h, self.r)
+ end
+
+ love.graphics.setColor(self.textColor)
+ love.graphics.setFont(self.font)
+
+ local text = type(self.text) == 'function' and self.text() or self.text
+
+ local lineAmount
+ do
+ local _, t = self.font:getWrap(text, self.w)
+ lineAmount = #t
+ end
+ local textHeight = self.font:getHeight() * (lineAmount * 0.5)
+ local textPos = self.y + (self.h * 0.5) - textHeight
+ love.graphics.printf(text, self.x, textPos, self.w, self.textOrientation)
+
+ love.graphics.setColor(self.borderColor)
+ love.graphics.rectangle('line', self.x, self.y, self.w, self.h, self.r)
+end
+---Check if current position is hovering the button, if it is, return true
+function button:isHovering(x,y)
+ if not y then return false end
+ if
+ x >= self.x and
+ y >= self.y and
+ x <= self.x + self.w and
+ y <= self.y + self.h
+ then
+ return true
+ else
+ return false
+ end
+end
+---Trigger press action, only when ``self._hovering`` is true
+function button:press(x, y, touchID)
+ if (touchID and self:isHovering(x, y) or self._hovering) and not self._pressed then
+ self.codeWhenPressed()
+
+ self._touchID = touchID
+ self._pressed = true
+ self:draw()
+
+ return true
+ end
+end
+---Trigger release action, don't need ``self._hovering`` to ``true``
+function button:release(x, y, touchID)
+ local valid = true
+ if touchID then
+ valid = touchID == self._touchID
+ elseif x and y then
+ valid = true
+ end
+
+ if valid then
+ self._pressed = false
+ self._touchID = false
+
+ if touchID then
+ self._hovering = false
+ else
+ self._hovering = self:isHovering(x, y)
+ end
+
+ if self:isHovering(x, y) then
+ self.codeWhenReleased()
+ return true
+ end
+ end
+end
+
+---@param D BUTTON.button|BUTTON.newData
+---@param safe? boolean @ Creating widget? If not then ignore accept missing important parameters
+---@return nil
+---Validate the provided data, will be called by ``BUTTON.new`` and ``BUTTON.setDefaultOption``
+---***WARNING! THIS FUNCTION WILL RAISE EXCEPTION IF DATA IS INVALID!***
+function BUTTON.checkDataValidation(D, safe)
+ if not safe then
+ if type(D.text) == 'function' then
+ assert(type(D.text()) == 'string', "[text] is a function but it doesn't return any string?!")
+ elseif type(D.text) ~= 'string' then
+ error("[text] must be a string or a function returns string, got "..type(D.text))
+ end
+
+ assert(type(D.x) == "number" , "[x] must be a integer")
+ assert(type(D.y) == "number" , "[y] must be a integer")
+ assert(type(D.w) == "number" and D.w > 0, "[w] must be a positive integer")
+ assert(type(D.h) == "number" and D.h > 0, "[h] must be a positive integer")
+ assert((type(D.r) == "number" and D.r >= 0 and D.r <= D.w * 0.5 and D.r <= D.h * 0.5) or D.r == nil, "[r] must be a positive integer and cannot larger than half of button's width and half of button's height")
+ else
+ assert(type(D.r) == "number" and D.r >= 0 or D.r == nil, "[r] must be a positive integer (CAUTION: a extra condition is temproraily ignored because you are setting default option)")
+ end
+ assert(table.contains({"center","justify","left","right"}, D.textOrientation) or D.textOrientation == nil, "[borderJoin] must be 'bevel', 'miter' or 'none")
+ assert((type(D.borderWidth) == "number" and D.borderWidth > 0) or D.borderWidth == nil, "[borderWidth] must be a postive integer")
+ assert(table.contains({"bevel", "miter", "none"}, D.borderJoin) or D.borderJoin == nil, "[borderJoin] must be 'bevel', 'miter' or 'none")
+ assert(table.contains({"rough", "smooth"}, D.borderStyle) or D.borderStyle == nil, "[borderStyle] must be 'rough' or 'smooth'")
+ assert((D.font and D.font.typeOf and D.font:typeOf("Font")) or D.font == nil, "[font] must be love.Font")
+
+ assert(checkColorTableValidation(D.backgroundColor), "[backgroundColor] must be a table with r, g, b (, a) values, all of them must be integers between 0 and 1")
+ assert(checkColorTableValidation(D.hoverColor), "[hoverColor] must be a table with r, g, b (, a) values, all of them must be integers between 0 and 1")
+ assert(checkColorTableValidation(D.pressColor), "[hoverColor] must be a table with r, g, b (, a) values, all of them must be integers between 0 and 1")
+ assert(checkColorTableValidation(D.borderColor), "[borderColor] must be a table with r, g, b (, a) values, all of them must be integers between 0 and 1")
+ assert(checkColorTableValidation(D.textColor), "[textColor] must be a table with r, g, b (, a) values, all of them must be integers between 0 and 1")
+
+ assert(type(D.codeWhenPressed) == "function" or D.codeWhenPressed == nil, "[codeWhenPressed] must be a function or nil")
+ assert(type(D.codeWhenReleased) == "function" or D.codeWhenReleased == nil, "[codeWhenReleased] must be a function or nil")
+ assert(type(D.drawingButtonFunc) == "function" or D.drawingButtonFunc == nil, "[drawingButtonFunc] must be a function or nil")
+end
+
+---@class BUTTON.newData
+---@field text string|function # Name of the button, will be used to show. If function provided, it should return the string!
+---@field textOrientation? "center"|"justify"|"left"|"right"
+---
+---@field x number # Position of the button (x, y, w, h)
+---@field y number # Position of the button (x, y, w, h)
+---@field w number # Position of the button (x, y, w, h)
+---@field h number # Position of the button (x, y, w, h)
+---@field r? number # Radius corner, cannot larger than half of button's width and half of button's height
+---
+---@field borderWidth? number|1 # Line width will be used to draw button
+---@field borderJoin? "bevel"|"miter"|"none"
+---@field borderStyle? "rough"|"smooth"
+---
+---@field font? love.Font
+---
+---@field backgroundColor? integer[]
+---@field textColor? integer[]
+---@field borderColor? integer[]
+---@field hoverColor? integer[]
+---@field pressColor? integer[]
+---
+---@field codeWhenPressed? function| # Code will be execute when pressed
+---@field codeWhenReleased? function| # Code will be execute when released
+---@field drawingButtonFunc? function| # The function is used to draw text
You can override the default one if you feel the default text drawing function is not suitable for you
+---
+---@field draw? function
+---@field update? function
+
+---@param D BUTTON.newData
+---@nodiscard
+---Create a new button, provide you a table with 4 functions inside: draw and update, press and release
+---You need to put them into intended callbacks :)
+---
+---Remember to fill 5 necessary parameters: name, x, y, w and h
+function BUTTON.new(D)
+ local B = setmetatable(D, button)
+ BUTTON.checkDataValidation(B)
+ return B
+end
+
+---@param D BUTTON.button
+function BUTTON.setDefaultOption(D)
+ BUTTON.checkDataValidation(setmetatable(D, button), true)
+ for k, v in pairs(D) do
+ if button[k] ~= nil then
+ button[k] = v
+ else
+ error("Parameter named ["..k.."] is not existed or cannot be set default value, in BUTTON!")
+ end
+ end
+end
+
+-- < EXTRA GENERAL OPTIONS >
+
+---Draw all buttons in provided list
+---@param list table
+function BUTTON.draw(list)
+ for _, v in pairs(list) do v:draw() end
+end
+---Update all buttons in provided list
+---@param list table
+function BUTTON.update(list)
+ for _, v in pairs(list) do v:update() end
+end
+
+---Check if current mouse position is inside button
+---Calling BUTTON.press will trigger this, but you can call it when moving mouse so button can be highlighted when being hovered
+---@param list table
+---@param x number # Mouse position
+---@param y number # Mouse position
+function BUTTON.checkHovering(list, x, y)
+ local highlighted_a_button = false
+ for _, v in pairs(list) do
+ if highlighted_a_button then
+ v._hovering = false
+ else
+ v._hovering = v:isHovering(x, y)
+ end
+
+ if not highlighted_a_button and v._hovering then highlighted_a_button = true end
+ end
+end
+
+--- Trigger the press action, only if ``button._hovering == true``
+---@param list table
+---@param x number # Mouse position
+---@param y number # Mouse position
+function BUTTON.press(list, x, y, touchID)
+ for _, v in pairs(list) do if v:press(x, y, touchID) then return true end end
+end
+
+---Trigger the release action
+---@param list table
+function BUTTON.release(list, x, y, touchID)
+ for _, v in pairs(list) do if v:release(x, y, touchID) then return true end end
+end
+
+return BUTTON
\ No newline at end of file
diff --git a/libs/simple-slider.lua b/libs/simple-slider.lua
new file mode 100644
index 0000000..e5a8b3b
--- /dev/null
+++ b/libs/simple-slider.lua
@@ -0,0 +1,153 @@
+--[[
+Copyright (c) 2016 George Prosser
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+]]
+
+local slider = {}
+slider.__index = slider
+
+---@type table
+---@class slider.style
+---@field width? number
+---@field orientatio? 'horizontal'|'vertical'
+---@field track? 'rectangle'|'line'|'roundrect'
+---@field knob? 'rectangle'|'circle'
+
+---@param x number
+---@param y number
+---@param length number
+---@param value number
+---@param min number
+---@param max number
+---@param setter? function
+---@param style? slider.style
+function newSlider(x, y, length, value, min, max, setter, style)
+ local s = {}
+ s.value = (value - min) / (max - min)
+ s.min = min
+ s.max = max
+ s.setter = setter
+ s.x = x
+ s.y = y
+ s.length = length
+
+ local p = style or {}
+ s.width = p.width or length * 0.1
+ s.orientation = p.orientation or 'horizontal'
+ s.track = p.track or 'rectangle'
+ s.knob = p.knob or 'rectangle'
+
+ s.grabbed = false
+ s.wasDown = true
+ s.ox = 0
+ s.oy = 0
+
+ return setmetatable(s, slider)
+end
+
+function slider:update(mouseX, mouseY, mouseDown)
+ local x = mouseX or love.mouse.getX()
+ local y = mouseY or love.mouse.getY()
+ local down = love.mouse.isDown(1)
+ if mouseDown ~= nil then
+ down = mouseDown
+ end
+
+ local knobX = self.x
+ local knobY = self.y
+ if self.orientation == 'horizontal' then
+ knobX = self.x - self.length/2 + self.length * self.value
+ elseif self.orientation == 'vertical' then
+ knobY = self.y + self.length/2 - self.length * self.value
+ end
+
+ local ox = x - knobX
+ local oy = y - knobY
+
+ local dx = ox - self.ox
+ local dy = oy - self.oy
+
+ if down then
+ if self.grabbed then
+ if self.orientation == 'horizontal' then
+ self.value = self.value + dx / self.length
+ elseif self.orientation == 'vertical' then
+ self.value = self.value - dy / self.length
+ end
+ elseif (x > knobX - self.width/2 and x < knobX + self.width/2 and y > knobY - self.width/2 and y < knobY + self.width/2) and not self.wasDown then
+ self.ox = ox
+ self.oy = oy
+ self.grabbed = true
+ end
+ else
+ self.grabbed = false
+ end
+
+ self.value = math.max(0, math.min(1, self.value))
+
+ if self.setter ~= nil then
+ self.setter(self.min + self.value * (self.max - self.min))
+ end
+
+ self.wasDown = down
+end
+
+function slider:draw()
+ if self.track == 'rectangle' then
+ if self.orientation == 'horizontal' then
+ love.graphics.rectangle('line', self.x - self.length/2 - self.width/2, self.y - self.width/2, self.length + self.width, self.width)
+ elseif self.orientation == 'vertical' then
+ love.graphics.rectangle('line', self.x - self.width/2, self.y - self.length/2 - self.width/2, self.width, self.length + self.width)
+ end
+ elseif self.track == 'line' then
+ if self.orientation == 'horizontal' then
+ love.graphics.line(self.x - self.length/2, self.y, self.x + self.length/2, self.y)
+ elseif self.orientation == 'vertical' then
+ love.graphics.line(self.x, self.y - self.length/2, self.x, self.y + self.length/2)
+ end
+ elseif self.track == 'roundrect' then
+ if self.orientation == 'horizontal' then
+ love.graphics.rectangle('line', self.x - self.length/2 - self.width/2, self.y - self.width/2, self.length + self.width, self.width, self.width/2, self.width)
+ elseif self.orientation == 'vertical' then
+ love.graphics.rectangle('line', self.x - self.width/2, self.y - self.length/2 - self.width/2, self.width, self.length + self.width, self.width, self.width/2)
+ end
+ end
+
+ local knobX = self.x
+ local knobY = self.y
+ if self.orientation == 'horizontal' then
+ knobX = self.x - self.length/2 + self.length * self.value
+ elseif self.orientation == 'vertical' then
+ knobY = self.y + self.length/2 - self.length * self.value
+ end
+
+ if self.knob == 'rectangle' then
+ love.graphics.rectangle('fill', knobX - self.width/2, knobY - self.width/2, self.width, self.width)
+ elseif self.knob == 'circle' then
+ love.graphics.circle('fill', knobX, knobY, self.width/2)
+ end
+end
+
+function slider:getValue()
+ return self.min + self.value * (self.max - self.min)
+end
\ No newline at end of file
diff --git a/load.lua b/load.lua
new file mode 100644
index 0000000..194df63
--- /dev/null
+++ b/load.lua
@@ -0,0 +1,169 @@
+-- Fonts
+FONT_tromi = love.graphics.newFont('res/fonts/Iosevka-Bold.ttf' , 28)
+FONT_big = love.graphics.newFont('res/fonts/Iosevka-Heavy.ttf', 56)
+FONT_bold = love.graphics.newFont('res/fonts/Iosevka-Heavy.ttf', 28)
+-- Icons
+FONT_tromi:setFallbacks(love.graphics.newFont('res/fonts/techmino_proportional.otf', 28))
+FONT_bold :setFallbacks(love.graphics.newFont('res/fonts/techmino_proportional.otf', 28))
+FONT_big :setFallbacks(love.graphics.newFont('res/fonts/techmino_proportional.otf', 56))
+CHAR = require("char")
+
+local font_height = FONT_tromi:getHeight() * 0.5
+local font_big_height = FONT_big:getHeight() * 0.5
+
+-- Bigint library
+bigint = require "libs.bigint.bigint"
+number_names = require "libs.bigint.named-powers-of-ten"
+
+-- BUTTON library
+require "libs.simple-slider"
+BUTTON = require "libs.simple-button"
+BUTTON.setDefaultOption{
+ draw = function(self)
+ local need_big_font = (self.font == FONT_big)
+
+ love.graphics.setColor(self.backgroundColor)
+ love.graphics.rectangle('fill', self.x, self.y, self.w, self.h, self.r)
+
+ if self._pressed then
+ love.graphics.setColor(self.pressColor)
+ love.graphics.rectangle('fill', self.x, self.y, self.w, self.h, self.r)
+ elseif self._hovering then
+ love.graphics.setColor(self.hoverColor)
+ love.graphics.rectangle('fill', self.x, self.y, self.w, self.h, self.r)
+ end
+
+ local text = type(self.text) == 'function' and self.text() or self.text
+
+ local lineAmount
+ do
+ local _, t = self.font:getWrap(text, (self.w - 5) * 2)
+ lineAmount = #t
+ end
+
+ local _font_height = need_big_font and font_big_height or font_height
+
+ local textHeight = _font_height * (lineAmount * 0.5)
+ local textPos = self.y + (self.h * 0.5) - textHeight
+
+ if self.font == FONT_big then
+ drawBigText(text, self.x + 2.5, textPos, self.w - 5, self.textOrientation, self.textColor)
+ elseif self.font == FONT_bold then
+ drawBoldText(text, self.x + 2.5, textPos, self.w - 5, self.textOrientation, self.textColor)
+ else
+ drawText(text, self.x + 2.5, textPos, self.w - 5, self.textOrientation, self.textColor)
+ end
+
+ love.graphics.setColor(self.borderColor)
+ love.graphics.setLineWidth(1)
+ love.graphics.rectangle('line', self.x, self.y, self.w, self.h, self.r)
+ end,
+ backgroundColor = {0, 0, 0, 0.8},
+ pressColor = {0.4, 1, 1, 0.5},
+ borderColor = {1, 1, 1, 0.8},
+}
+
+-- Graphics
+ShowLoadingText('backgrounds')
+local gc_newImage, gc_newVideo = love.graphics.newImage, love.graphics.newVideo
+BACKGROUNDS = {
+ [0] = gc_newVideo("res/backgrounds/green_waterfall.ogv", {audio=false}),
+ gc_newVideo("res/backgrounds/water.ogv", {audio=false}),
+ gc_newVideo("res/backgrounds/green_streams.ogv", {audio=false}),
+ gc_newVideo("res/backgrounds/streams.ogv", {audio=false}),
+ gc_newVideo("res/backgrounds/red_forest_waterfall.ogv", {audio=false}),
+ gc_newVideo("res/backgrounds/flowers_rain.ogv", {audio=false}),
+ gc_newVideo("res/backgrounds/moonlight_tree.ogv", {audio=false}),
+ gc_newVideo("res/backgrounds/lisa_frank.ogv", {audio=false}),
+ gc_newVideo("res/backgrounds/snowy_trees.ogv", {audio=false}),
+ gc_newVideo("res/backgrounds/snowy_cabin.ogv", {audio=false}),
+}
+
+ShowLoadingText('blocks')
+BLOCKS = {
+ ["2tie"] = {
+ R = gc_newImage("res/img/r.png"),
+ O = gc_newImage("res/img/o.png"),
+ Y = gc_newImage("res/img/y.png"),
+ G = gc_newImage("res/img/g.png"),
+ C = gc_newImage("res/img/b.png"),
+ B = gc_newImage("res/img/i.png"),
+ M = gc_newImage("res/img/v.png"),
+ F = gc_newImage("res/img/bl.png"),
+ A = gc_newImage("res/img/bl.png"),
+ X = gc_newImage("res/img/t.png"),
+ W = gc_newImage("res/img/w.png"),
+ R_d = gc_newImage("res/img/r_d.png"),
+ O_d = gc_newImage("res/img/o_d.png"),
+ Y_d = gc_newImage("res/img/y_d.png"),
+ G_d = gc_newImage("res/img/g_d.png"),
+ C_d = gc_newImage("res/img/b_d.png"),
+ B_d = gc_newImage("res/img/i_d.png"),
+ M_d = gc_newImage("res/img/v_d.png"),
+ }
+}
+
+COLOUR_SCHEMES = {
+ Arika = {
+ I = "R",
+ L = "O",
+ J = "B",
+ S = "M",
+ Z = "G",
+ O = "Y",
+ T = "C",
+ }
+}
+
+-- BGMs and SFXs
+ShowLoadingText('BGMs & SFXs')
+local audio_newSource = love.audio.newSource
+SOUNDS = {
+ bottom = audio_newSource("res/se/bottom.wav", "static"),
+ lock = audio_newSource("res/se/lock.wav", "static"),
+ erase = audio_newSource("res/se/erase.wav", "static"),
+ fall = audio_newSource("res/se/fall.wav", "static"),
+ ready = audio_newSource("res/se/ready.wav", "static"),
+ promote = audio_newSource("res/se/promote.wav", "static"),
+ demote = audio_newSource("res/se/demote.wav", "static"),
+ autopromote = audio_newSource("res/se/autopromote.wav", "static"),
+ bgm_firsthalf = audio_newSource("res/bgm/firsthalf.flac", "static"),
+ bgm_secondhalf = audio_newSource("res/bgm/secondhalf.flac", "static"),
+ bgm_title = audio_newSource("res/bgm/title.flac", "static")
+}
+
+function PlaySE(sound, subsound)
+ if sound ~= nil then
+ if subsound ~= nil then
+ SOUNDS[sound][subsound]:setVolume(0.4)
+ if SOUNDS[sound][subsound]:isPlaying() then
+ SOUNDS[sound][subsound]:stop()
+ end
+ SOUNDS[sound][subsound]:play()
+ else
+ SOUNDS[sound]:setVolume(0.4)
+ if SOUNDS[sound]:isPlaying() then
+ SOUNDS[sound]:stop()
+ end
+ SOUNDS[sound]:play()
+ end
+ end
+end
+
+function PlaySEOnce(sound, subsound)
+ if sound ~= nil then
+ if subsound ~= nil then
+ SOUNDS[sound][subsound]:setVolume(0.4)
+ if SOUNDS[sound][subsound]:isPlaying() then
+ return
+ end
+ SOUNDS[sound][subsound]:play()
+ else
+ SOUNDS[sound]:setVolume(0.4)
+ if SOUNDS[sound]:isPlaying() then
+ return
+ end
+ SOUNDS[sound]:play()
+ end
+ end
+end
diff --git a/main.lua b/main.lua
new file mode 100644
index 0000000..e7da0d5
--- /dev/null
+++ b/main.lua
@@ -0,0 +1,606 @@
+if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE")=="1" then
+ LLDEBUGGER=require('lldebugger')
+ LLDEBUGGER.start()
+end
+require 'funcs'
+DEBUG_showKey = false
+
+PENTO_MODE = false
+SAVE_DIR = 'saves/'
+REPLAY_DIR = 'saves/replays/'
+if not love.filesystem.getInfo(REPLAY_DIR) then
+ love.filesystem.createDirectory(REPLAY_DIR)
+end
+CONFIG_FILE = 'config.sav'
+HIscoreFILE = 'hiscores.sav'
+
+CURRENT_OS = love.system.getOS()
+MOBILE = CURRENT_OS == "Android" or CURRENT_OS == "iOS"
+
+LOADING_IMAGE_FILE = love.graphics.newImage('res/loading.png')
+--- Show the loading text while we are loading resources
+--- **WARNING**: should only be used while loading the game!
+function ShowLoadingText(thing)
+ love.resize(love.graphics.getDimensions())
+ love.graphics.replaceTransform(GLOBAL_TRANSFORM)
+ love.graphics.setFont(love.graphics.newFont(20))
+ love.graphics.clear()
+
+ love.graphics.draw(LOADING_IMAGE_FILE,0,0,0,0.5)
+ drawText("Loading ".. thing .. "...", 15, 455, 1e99, "left")
+
+ love.graphics.flushBatch()
+ love.graphics.present()
+end
+
+function love.load()
+ math.randomseed(os.time())
+ require "modules.file" "binser"
+ require "settings"
+
+ -- Window stuffs
+ love.window.setMode(love.graphics.getWidth(), love.graphics.getHeight(), {resizable = true});
+ love.window.setFullscreen(SETTINGS["fullscreen"])
+
+ -- Make a new transformer
+ GLOBAL_TRANSFORM = love.math.newTransform()
+ love.resize(love.graphics.getWidth(), love.graphics.getHeight())
+
+ -- Now it's real time to load all stuffs!
+ require "load" -- Most game's resources are loaded in here
+ require "modules.scene"
+ require "game.vctrl" -- VCTRL
+
+ function SCENE.update()
+ SCENE.update = function() end
+ SCENE = SETTINGS.firstTime and InputConfigScene(true) or TitleScene()
+ end
+ function SCENE.render()
+ SCENE.render = function() end
+ love.graphics.draw(LOADING_IMAGE_FILE,0,0,0,0.5)
+ end
+
+ -- VCTRL.toggle(love.system.getOS()=='Android' or true)
+ -- VCTRL.new{ -- up down left right --- right left down up
+ -- -- {type='button',x= 100,y=320,key= 'up',r=35,iconSize=60,alpha=0.75},
+ -- -- {type='button',x= 100,y=440,key= 'down',r=35,iconSize=60,alpha=0.75},
+ -- -- {type='button',x= 40,y=380,key= 'left',r=35,iconSize=60,alpha=0.75},
+ -- -- {type='button',x= 160,y=380,key= 'right',r=35,iconSize=60,alpha=0.75},
+ -- -- {type='button',x=640- 40,y=380,key= 'rotate_left',r=35,iconSize=60,alpha=0.75},
+ -- -- {type='button',x=640-160,y=380,key= 'rotate_left2',r=35,iconSize=60,alpha=0.75},
+ -- -- {type='button',x=640-100,y=440,key= 'rotate_right',r=35,iconSize=60,alpha=0.75},
+ -- -- {type='button',x=640-100,y=320,key='rotate_right2',r=35,iconSize=60,alpha=0.75},
+ -- }
+ VCTRL.new(SETTINGS.input.virtual)
+end
+
+function love.resize(w, h)
+ SCREEN_SCALE_FACTOR = math.min(w / 640, h / 480)
+ GLOBAL_TRANSFORM:setTransformation(
+ (w - SCREEN_SCALE_FACTOR * 640) / 2,
+ (h - SCREEN_SCALE_FACTOR * 480) / 2,
+ 0,
+ SCREEN_SCALE_FACTOR
+ )
+end
+
+function love.draw()
+ love.graphics.replaceTransform(GLOBAL_TRANSFORM)
+ love.graphics.clear()
+ love.graphics.push()
+
+ SCENE:render()
+
+ -- -- Grid system
+ -- local grid_width, grid_height = 40, 20
+ -- love.graphics.setColor(1,1,1,0.5)
+ -- love.graphics.setLineWidth(1)
+ -- -- From 0 to X
+ -- for ix=0,math.floor(640 / grid_width) do
+ -- love.graphics.line(grid_width * ix, 0 , grid_width * ix, 480)
+ -- end
+ -- -- From 0 to Y
+ -- for iy=0,math.floor(480 / grid_height) do
+ -- love.graphics.line(0, grid_height * iy, 640, grid_height * iy)
+ -- end
+
+ -- local x, y = GLOBAL_TRANSFORM:inverseTransformPoint(love.mouse.getPosition())
+ -- love.graphics.setColor(0, 0, 0, 0.8)
+ -- love.graphics.rectangle("fill", 5, 450, 115, 25)
+ -- drawText(string.format("X: %.1d; Y: %.1d", x, y), 10, 455, 110, "left")
+
+ -- love.graphics.setColor(1, 1, 1, 1)
+ -- love.graphics.setLineWidth(2)
+ -- love.graphics.rectangle("line", 0, 0, 640, 480)
+
+ love.graphics.pop()
+end
+
+function love.mousepressed(x, y, b, isTouch, presses)
+ if isTouch then return end
+ local x,y=GLOBAL_TRANSFORM:inverseTransformPoint(x,y)
+ SCENE:onInputPress{type = "mouse", x = x, y = y, presses = presses}
+end
+function love.mousereleased(x, y, b, isTouch, presses)
+ if isTouch then return end
+ local x,y=GLOBAL_TRANSFORM:inverseTransformPoint(x,y)
+ SCENE:onInputRelease{type = "mouse", x = x, y = y, presses = presses}
+end
+function love.mousemoved(x, y, dx, dy, isTouch)
+ if isTouch then return end
+ local x,y=GLOBAL_TRANSFORM:inverseTransformPoint(x,y)
+ local dx,dy=dx/SCREEN_SCALE_FACTOR,dy/SCREEN_SCALE_FACTOR
+ SCENE:onInputMove{type = "mouse", x = x, y = y, dx = dx, dy = dy}
+end
+function love.wheelmoved(dx, dy)
+ SCENE:onInputMove{type = "wheel", dx = dx, dy = dy}
+end
+
+function love.touchpressed(id,x,y)
+ local x,y=GLOBAL_TRANSFORM:inverseTransformPoint(x,y)
+ SCENE:onInputPress{type = "touch", x = x, y = y, dx = 0, dy = 0, id = id}
+end
+function love.touchmoved(id,x,y,dx,dy)
+ local x,y=GLOBAL_TRANSFORM:inverseTransformPoint(x,y)
+ SCENE:onInputMove{type = "touch", x = x, y = y, dx = dx, dy = dy, id = id}
+end
+function love.touchreleased(id,x,y)
+ local x,y=GLOBAL_TRANSFORM:inverseTransformPoint(x,y)
+ SCENE:onInputRelease{type = "touch", x = x, y = y, dx = 0, dy = 0, id = id}
+end
+
+function love.keypressed(key, scancode)
+ local input_pressed=nil
+
+ -- global hotkeys
+ if scancode == "f4" then
+ SETTINGS["fullscreen"] = not SETTINGS["fullscreen"]
+ love.window.setFullscreen(SETTINGS["fullscreen"])
+ elseif scancode == "f2" and SCENE.title ~= "Input Config" and SCENE.title ~= "Game" then
+ SCENE = InputConfigScene()
+ elseif scancode == "f12" then LLDEBUGGER.requestBreak()
+ -- elseif scancode == "f11" then SETTINGS.firstTime = true
+ -- function keys are reserved
+ elseif string.match(scancode, "^f[1-9]$") or string.match(scancode, "^f1[0-2]+$") then
+ return
+ -- escape is reserved for menu_back
+ elseif scancode == "escape" then
+ SCENE:onInputPress{input="menu_back", type="key", key=key, scancode=scancode}
+ -- pass any other key to the scene, with its configured mapping
+ else
+ if SETTINGS.input and SETTINGS.input.keys then
+ input_pressed = SETTINGS.input.keys[scancode]
+ end
+ SCENE:onInputPress{input=input_pressed, type="key", key=key, scancode=scancode}
+ end
+end
+
+function love.keyreleased(key, scancode)
+ local input_released = nil
+
+ -- escape is reserved for menu_back
+ if scancode == "escape" or scancode == 'acback' then
+ SCENE:onInputRelease{input="menu_back", type="key", key=key, scancode=scancode}
+ -- function keys are reserved
+ elseif string.match(scancode, "^f[1-9]$") or string.match(scancode, "^f1[0-2]+$") then
+ return
+ -- handle all other keys; tab is reserved, but the input config scene keeps it from getting configured as a game input, so pass tab to the scene here
+ else
+ if SETTINGS.input and SETTINGS.input.keys then
+ input_released = SETTINGS.input.keys[scancode]
+ end
+ SCENE:onInputRelease{input=input_released, type="key", key=key, scancode=scancode}
+ end
+end
+
+function love.joystickpressed(joystick, button)
+ local input_pressed = nil
+ if
+ SETTINGS.input and
+ SETTINGS.input.joysticks and
+ SETTINGS.input.joysticks[joystick:getName()] and
+ SETTINGS.input.joysticks[joystick:getName()].buttons
+ then
+ input_pressed = SETTINGS.input.joysticks[joystick:getName()].buttons[button]
+ end
+ SCENE:onInputPress({input=input_pressed, type="joybutton", name=joystick:getName(), button=button})
+end
+
+function love.joystickreleased(joystick, button)
+ local input_released = nil
+ if
+ SETTINGS.input and
+ SETTINGS.input.joysticks and
+ SETTINGS.input.joysticks[joystick:getName()] and
+ SETTINGS.input.joysticks[joystick:getName()].buttons
+ then
+ input_released = SETTINGS.input.joysticks[joystick:getName()].buttons[button]
+ end
+ SCENE:onInputRelease({input=input_released, type="joybutton", name=joystick:getName(), button=button})
+end
+
+function love.joystickaxis(joystick, axis, value)
+ local input_pressed = nil
+ local positive_released = nil
+ local negative_released = nil
+ if
+ SETTINGS.input and
+ SETTINGS.input.joysticks and
+ SETTINGS.input.joysticks[joystick:getName()] and
+ SETTINGS.input.joysticks[joystick:getName()].axes and
+ SETTINGS.input.joysticks[joystick:getName()].axes[axis]
+ then
+ if math.abs(value) >= 1 then
+ input_pressed = SETTINGS.input.joysticks[joystick:getName()].axes[axis][value >= 1 and "positive" or "negative"]
+ end
+ positive_released = SETTINGS.input.joysticks[joystick:getName()].axes[axis].positive
+ negative_released = SETTINGS.input.joysticks[joystick:getName()].axes[axis].negative
+ end
+ if math.abs(value) >= 1 then
+ SCENE:onInputPress({input=input_pressed, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
+ else
+ SCENE:onInputRelease({input=positive_released, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
+ SCENE:onInputRelease({input=negative_released, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
+ end
+end
+
+local last_hat_direction = ""
+local directions = {
+ ["u"] = "up",
+ ["d"] = "down",
+ ["l"] = "left",
+ ["r"] = "right",
+}
+
+function love.joystickhat(joystick, hat, direction)
+ local input_pressed = nil
+ local has_hat = false
+ if
+ SETTINGS.input and
+ SETTINGS.input.joysticks and
+ SETTINGS.input.joysticks[joystick:getName()] and
+ SETTINGS.input.joysticks[joystick:getName()].hats and
+ SETTINGS.input.joysticks[joystick:getName()].hats[hat]
+ then
+ if direction ~= "c" then
+ input_pressed = SETTINGS.input.joysticks[joystick:getName()].hats[hat][direction]
+ end
+ has_hat = true
+ end
+ if input_pressed then
+ for i = 1, #direction do
+ local char = direction:sub(i, i)
+ local _, count = last_hat_direction:gsub(char, char)
+ if count == 0 then
+ SCENE:onInputPress({input=SETTINGS.input.joysticks[joystick:getName()].hats[hat][char], type="joyhat", name=joystick:getName(), hat=hat, direction=char})
+ end
+ end
+ for i = 1, #last_hat_direction do
+ local char = last_hat_direction:sub(i, i)
+ local _, count = direction:gsub(char, char)
+ if count == 0 then
+ SCENE:onInputRelease({input=SETTINGS.input.joysticks[joystick:getName()].hats[hat][char], type="joyhat", name=joystick:getName(), hat=hat, direction=char})
+ end
+ end
+ last_hat_direction = direction
+ elseif has_hat then
+ for i, direction in ipairs{"d", "l", "ld", "lu", "r", "rd", "ru", "u"} do
+ SCENE:onInputRelease({input=SETTINGS.input.joysticks[joystick:getName()].hats[hat][direction], type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
+ end
+ last_hat_direction = ""
+ elseif direction ~= "c" then
+ for i = 1, #direction do
+ local char = direction:sub(i, i)
+ local _, count = last_hat_direction:gsub(char, char)
+ if count == 0 then
+ SCENE:onInputPress({input=directions[char], type="joyhat", name=joystick:getName(), hat=hat, direction=char})
+ end
+ end
+ for i = 1, #last_hat_direction do
+ local char = last_hat_direction:sub(i, i)
+ local _, count = direction:gsub(char, char)
+ if count == 0 then
+ SCENE:onInputRelease({input=directions[char], type="joyhat", name=joystick:getName(), hat=hat, direction=char})
+ end
+ end
+ last_hat_direction = direction
+ else
+ for i, direction in ipairs{"d", "l", "ld", "lu", "r", "rd", "ru", "u"} do
+ SCENE:onInputRelease({input=nil, type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
+ end
+ last_hat_direction = ""
+ end
+end
+
+function love.focus(f) end
+
+local TARGET_FPS = 60
+
+function love.run()
+ if love.load then love.load(love.arg.parseGameArguments(arg), arg) end
+
+ if love.timer then love.timer.step() end
+
+ local dt = 0
+
+ local last_time = love.timer.getTime()
+ local time_accumulator = 0
+ return function()
+ if love.event then
+ love.event.pump()
+ for name, a,b,c,d,e,f in love.event.poll() do
+ if name == "quit" then
+ if not love.quit or not love.quit() then
+ return a or 0
+ end
+ end
+ love.handlers[name](a,b,c,d,e,f)
+ end
+ end
+
+ if SCENE and SCENE.update and love.timer then
+ SCENE:update()
+
+ local frame_duration = 1.0 / TARGET_FPS
+ if time_accumulator < frame_duration then
+ if love.graphics and love.graphics.isActive() and love.draw then
+ love.graphics.origin()
+ love.graphics.clear(love.graphics.getBackgroundColor())
+ love.draw()
+ love.graphics.present()
+ end
+ local end_time = last_time + frame_duration
+ local time = love.timer.getTime()
+ while time < end_time do
+ love.timer.sleep(0.001)
+ time = love.timer.getTime()
+ end
+ time_accumulator = time_accumulator + time - last_time
+ end
+ time_accumulator = time_accumulator - frame_duration
+ end
+ last_time = love.timer.getTime()
+ end
+end
+
+function love.errorhandler(msg)
+ local msg = msg or "I don't know if mycophobia's real name is \"mycophobiatrophilusiania\" or not.\nBUT WHO ATE THE ERROR MESSAGE?! DTET_ENJOYER, DID YOU?\n"
+ local showScreenshot = false
+ local errorCopied = false
+ local enter_fullscreen = SETTINGS and SETTINGS["fullscreen"] or false
+
+ -- Handling the error
+ local err={"Error:"..msg}
+ local c=2
+ for l in debug.traceback("",2):gmatch("(.-)\n") do
+ if c>2 then
+ if not l:find("boot") then
+ err[c]=l:gsub("^\t*","\t")
+ c=c+1
+ end
+ else
+ err[2]="Traceback"
+ c=3
+ end
+ end
+ print("\n"..table.concat(err,"\n",1,c-2))
+ local tracebacks = table.concat(err,"\n", 4)
+
+ if drawText == nil then
+ pcall(function()
+ FONT_tromi = love.graphics.newFont('res/fonts/monofonto rg.otf', 20)
+ require "funcs"
+ end)
+ if drawText == nil then
+ love.window.setMode(640, 480, {resizable = true})
+ return function() -- If "funcs" failed to load, we can only return a more simple version of error screen
+ love.event.pump()
+ for e, a, b, c in love.event.poll() do
+ if e == "quit" then return 1 end
+ end
+
+ love.graphics.origin()
+ love.graphics.clear()
+ love.graphics.setColor(1, 1, 1)
+ love.graphics.setFont(FONT_tromi)
+ love.graphics.printf(
+ err[1]:sub(7).."\nTraceback:\n"..tracebacks,
+ 30, 30, love.graphics.getWidth() - 10, "left"
+ )
+ love.graphics.present()
+ if love.timer then love.timer.sleep(0.1) end
+ end
+ end
+ end
+
+ -- Try to create a canva
+ love.graphics.origin()
+ local screenshot_canva, screenshot_canva_scale
+ local ok, _ = pcall(function()
+ if love.graphics.getSystemLimits().texturesize >= 1280 then
+ screenshot_canva = love.graphics.newCanvas(1280, 960)
+ screenshot_canva_scale = 0.5
+ else
+ error()
+ end
+ end)
+ if not ok then
+ screenshot_canva = love.graphics.newCanvas(640, 480)
+ screenshot_canva_scale = 1
+ end
+ -- Then draw everything again
+ love.graphics.setCanvas(screenshot_canva)
+ pcall(
+ function()
+ love.graphics.origin()
+ local transformer = love.math.newTransform(0, 0, 0, 2, 2)
+ love.graphics.replaceTransform(transformer)
+ SCENE:render()
+ end
+ )
+ love.audio.stop()
+ love.graphics.setCanvas()
+
+ local function draw()
+ love.graphics.origin()
+ love.graphics.replaceTransform(GLOBAL_TRANSFORM)
+ love.graphics.clear()
+
+ love.graphics.setColor(1, 1, 1)
+ love.graphics.draw(screenshot_canva, 0, 0, 0, screenshot_canva_scale)
+
+ if not showScreenshot then
+ love.graphics.setColor(0, 0, 0, 0.75)
+ love.graphics.rectangle("fill", 0, 0, 640, 480)
+ drawText([[
+OH NO! Tromi has crashed.
+Since this is not the official port, please do not report any bugs to mycophobia.
+Instead, report this to me via my Discord ``sweetsea'' with a screenshot of this.
+
+REMEMBER TO SCREENSHOT ERROR INFO BEFORE QUITTING BECAUSE THEY ARE NOT SAVED!
+
+Ctrl + C: copy the error info | If you click / tap, a window appears with 4 options:
+Space : show/hide screenshot | OK : Quit Copy: copy error info
+Escape : Quit | Cancel: Go back Show: show/hide screenshot
+]], 20, 10, 620, "left")
+
+ drawText(err[1]:sub(7).."\nTraceback:"..(errorCopied and " (Copied to clipboard)\n" or "\n")..tracebacks, 20, 180, 620, "left")
+ else
+ love.graphics.setColor(0, 0, 0, 0.8)
+ love.graphics.rectangle("fill", 15, 450, 400, 25, 5, 5)
+ drawText("Tromi has crashed! Press Space or tap to show error info", 15, 455, 400, "left")
+ end
+
+ love.graphics.present()
+ end
+
+ local fullErrorText = tracebacks
+ local function copyToClipboard()
+ love.system.setClipboardText(fullErrorText)
+ errorCopied = true
+ end
+
+ local buttons = {"OK", "Cancel", "Copy", "Show"}
+
+ return function()
+ love.event.pump()
+
+ for e, a, b, c in love.event.poll() do
+ if e == "quit" then
+ return 1
+ elseif e == "keypressed" and a == "escape" then
+ return 1
+ elseif e == "keypressed" and a == "c" and love.keyboard.isDown("lctrl", "rctrl") then
+ copyToClipboard()
+ elseif e == "keypressed" and a == "space" then
+ showScreenshot = not showScreenshot
+ elseif e == "keypressed" and a == "f4" then
+ enter_fullscreen = not enter_fullscreen
+ love.window.setFullscreen(enter_fullscreen)
+ elseif e == "mousepressed" then
+ local pressed = love.window.showMessageBox("Quit? Screenshot? Copy?", "Remember to screenshot error info before quitting because they are not saved!", buttons)
+ if pressed == 1 then
+ return 1
+ elseif pressed == 3 then
+ copyToClipboard()
+ elseif pressed == 4 then
+ showScreenshot = not showScreenshot
+ end
+ elseif e == "resize" then
+ love.resize(love.graphics.getDimensions())
+ end
+ end
+
+ draw()
+
+ if love.timer then
+ love.timer.sleep(0.1)
+ end
+ end
+
+end
+
+local minos = {'R_d', 'O_d', 'Y_d', 'G_d', 'C_d', 'B_d', 'M_d'}
+local main_bg_grid = {}
+for x=1, 40 do
+ main_bg_grid[x] = {}
+ for y=1, 30 do
+ main_bg_grid[x][y] = 0
+ end
+end
+local main_bg_cur_pos = {20,6}
+local main_bg_cur_color = minos[love.math.random(1,7)]
+local main_bg_cur_mino = 1
+local main_bg_draw_frame = 0
+local main_bg_last_color = nil
+
+function MainBackground()
+ if SETTINGS["music"] and not SOUNDS["bgm_title"]:isPlaying() then
+ SOUNDS["bgm_title"]:setVolume(0.3)
+ SOUNDS["bgm_title"]:play()
+ end
+ local y = 40
+ if main_bg_draw_frame >= 16 then
+ while y > 1 do
+ for x = 1, 40 do
+ main_bg_grid[x][y] = main_bg_grid[x][y-1]
+ end
+ y = y - 1
+ end
+ for x=1, 40 do
+ main_bg_grid[x][1] = 0
+ end
+ main_bg_draw_frame = 0
+ main_bg_cur_pos[2] = main_bg_cur_pos[2] + 1
+ end
+ local directions = { {0,-1},{1,0}, {-1,0}}
+ local test_dir = directions[love.math.random(1,3)]
+ main_bg_cur_pos[1] = main_bg_cur_pos[1] + test_dir[1]
+ main_bg_cur_pos[2] = main_bg_cur_pos[2] + test_dir[2]
+ if main_bg_cur_pos[1] > 40 then main_bg_cur_pos[1] = 40 end
+ if main_bg_cur_pos[1] < 1 then main_bg_cur_pos[1] = 1 end
+ if main_bg_cur_pos[2] > 30 then main_bg_cur_pos[2] = 30 end
+ if main_bg_cur_pos[2] < 1 then main_bg_cur_pos[2] = 1 end
+ if main_bg_grid[main_bg_cur_pos[1]][main_bg_cur_pos[2]] == 0 then
+ main_bg_grid[main_bg_cur_pos[1]][main_bg_cur_pos[2]] = main_bg_cur_color
+ main_bg_cur_mino = main_bg_cur_mino + 1
+ end
+ for x=1,40 do
+ for y=1,30 do
+ if main_bg_grid[x][y] ~= 0 then
+ love.graphics.setColor(1, 1, 1, 0.4)
+ if ((x-1)*48)-560 > 0 and ((x-1)*48)-560 < 640 then love.graphics.draw(BLOCKS["2tie"][main_bg_grid[x][y]], ((x-1)*48)-570, (((y+2)*48)+main_bg_draw_frame*3)-480,0, 3) end
+ love.graphics.setColor(1, 1, 1, 1)
+ end
+ end
+ end
+ for x=1,40 do
+ for y=1,30 do
+ if main_bg_grid[x][y] ~= 0 then
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.draw(BLOCKS["2tie"][main_bg_grid[x][y]], (x-1)*16, ((y-1)*16)+main_bg_draw_frame)
+ end
+ end
+ end
+ for x=1,40 do
+ for y=1,30 do
+ if main_bg_grid[x][y] ~= 0 then
+ love.graphics.setColor(1, 1, 1, 0.6)
+ if ((x-1)*32)-320 > 0 and ((x-1)*32)-320 < 640 then love.graphics.draw(BLOCKS["2tie"][main_bg_grid[x][y]], ((x-1)*32)-320, (((y+1)*32)+main_bg_draw_frame*2)-320,0, 2) end
+ love.graphics.setColor(1, 1, 1, 1)
+ end
+ end
+ end
+ if main_bg_cur_mino == 5 then
+ --if main_bg_cur_pos[2] < 4 then
+ -- main_bg_cur_pos[2] = 4
+ -- main_bg_cur_pos[1] = love.math.random(4, 36)
+ --end
+ main_bg_cur_pos = {love.math.random(16,24),6}
+ main_bg_last_color = main_bg_cur_color
+ while main_bg_cur_color == main_bg_last_color do main_bg_cur_color = minos[love.math.random(1,7)] end
+ main_bg_cur_mino = 1
+ end
+ main_bg_placed = false
+ main_bg_draw_frame = main_bg_draw_frame + 1
+end
diff --git a/modules/file.lua b/modules/file.lua
new file mode 100644
index 0000000..9c09db7
--- /dev/null
+++ b/modules/file.lua
@@ -0,0 +1,41 @@
+local FILE = {}
+local binser = require "libs.binser"
+local bitser = require "libs.bitser"
+
+local serializer_used
+
+function FILE.serialize(data)
+ if serializer_used == 'bitser' then
+ return bitser.dumps(data)
+ else
+ return binser.serialize(data)
+ end
+end
+
+function FILE.deserialize(data)
+ if serializer_used == 'bitser' then
+ return bitser.loads(data)
+ else
+ return binser.deserialize(data)[1]
+ end
+end
+
+function FILE.read(path)
+ if love.filesystem.getInfo(path) then
+ return FILE.deserialize(love.filesystem.read(path))
+ else
+ error("No file: "..path)
+ end
+end
+
+function FILE.write(path, data)
+ love.filesystem.write(path, FILE.serialize(data))
+end
+
+---@param lib_name 'bitser'|'binser'
+---Init the FILE module with chosen serializer
+return function(lib_name)
+ assert(lib_name == 'bitser' or lib_name == 'binser', '[lib_name] must be "bitser" or "binser"')
+ serializer_used = lib_name
+ _G.FILE = FILE
+end
\ No newline at end of file
diff --git a/modules/scene.lua b/modules/scene.lua
new file mode 100644
index 0000000..d3475ac
--- /dev/null
+++ b/modules/scene.lua
@@ -0,0 +1,55 @@
+local Object = require "libs.classic"
+
+SCENE = Object:extend()
+
+function SCENE:new() end
+function SCENE:update() end
+function SCENE:render() end
+
+-- You can use the class SCENE_onInput to show suggestions for `e` table
+
+---@class SCENE_onInput
+---@field type "key"|"joystick"|"virtual"|"touch"|"mouse"|"wheel"
+---
+---@field input? string # Action triggered
Only visible via keyboard and gamepad
+---@field key? love.KeyConstant Key pressed? Only visible via keyboard and gamepad
+---@field scancode? love.Scancode Key pressed but on the US layout? Only visible via keyboard and gamepad
+---
+---@field x? number Only visible via touch and mouse
+---@field y? number Only visible via touch and mouse
+---@field dx? number # Delta X
Only visible via touch, mouse and wheel
+---@field dy? number # Delta Y
Only visible via touch, mouse and wheel
+---@field id? lightuserdata # Only visible via touch
+---@field presses? number # Only visible via mouse
+
+-- e in 4 below functions will contain different things based on it's type:
+-- key - input, key, scancode
+-- joystick - input, button, name
+-- virtual - input
+-- touch - x, y, dx, dy, id
+-- mouse - x, y, dx, dy, presses
+-- wheel - dx, dy
+
+function SCENE:onInputMove(e) end
+function SCENE:onInputPress(e) end
+function SCENE:onInputRelease(e) end
+
+GameScene = require "scene.game"
+TrainingScene = require "scene.training"
+NameEntryScene = require "scene.name_entry"
+
+KeyConfigScene = require "scene.key_config"
+StickConfigScene = require "scene.stick_config"
+TouchConfigScene = require "scene.touch_config"
+TouchConfigPreviewScene = require "scene.touch_config_preview"
+InputConfigScene = require "scene.input_config"
+
+ReplaySelectScene = require "scene.replay"
+ReplayTestScene = require"scene.replay_test"
+
+FullscreenScene = require "scene.fullscreen"
+MusicToggleScene = require "scene.music_toggle"
+LinesToggleScene = require "scene.lines_toggle"
+
+ExitScene = require "scene.exit"
+TitleScene = require "scene.title"
\ No newline at end of file
diff --git a/res/backgrounds/flowers_rain.ogv b/res/backgrounds/flowers_rain.ogv
new file mode 100644
index 0000000..843eaaf
Binary files /dev/null and b/res/backgrounds/flowers_rain.ogv differ
diff --git a/res/backgrounds/green_streams.ogv b/res/backgrounds/green_streams.ogv
new file mode 100644
index 0000000..612b156
Binary files /dev/null and b/res/backgrounds/green_streams.ogv differ
diff --git a/res/backgrounds/green_waterfall.ogv b/res/backgrounds/green_waterfall.ogv
new file mode 100644
index 0000000..2c2695e
Binary files /dev/null and b/res/backgrounds/green_waterfall.ogv differ
diff --git a/res/backgrounds/lisa_frank.ogv b/res/backgrounds/lisa_frank.ogv
new file mode 100644
index 0000000..23d095c
Binary files /dev/null and b/res/backgrounds/lisa_frank.ogv differ
diff --git a/res/backgrounds/moonlight_tree.ogv b/res/backgrounds/moonlight_tree.ogv
new file mode 100644
index 0000000..61f12e5
Binary files /dev/null and b/res/backgrounds/moonlight_tree.ogv differ
diff --git a/res/backgrounds/red_forest_waterfall.ogv b/res/backgrounds/red_forest_waterfall.ogv
new file mode 100644
index 0000000..f7a00e9
Binary files /dev/null and b/res/backgrounds/red_forest_waterfall.ogv differ
diff --git a/res/backgrounds/snowy_cabin.ogv b/res/backgrounds/snowy_cabin.ogv
new file mode 100644
index 0000000..a73fff1
Binary files /dev/null and b/res/backgrounds/snowy_cabin.ogv differ
diff --git a/res/backgrounds/snowy_trees.ogv b/res/backgrounds/snowy_trees.ogv
new file mode 100644
index 0000000..478ff82
Binary files /dev/null and b/res/backgrounds/snowy_trees.ogv differ
diff --git a/res/backgrounds/streams.ogv b/res/backgrounds/streams.ogv
new file mode 100644
index 0000000..571c1c3
Binary files /dev/null and b/res/backgrounds/streams.ogv differ
diff --git a/res/backgrounds/water.ogv b/res/backgrounds/water.ogv
new file mode 100644
index 0000000..2b0f77e
Binary files /dev/null and b/res/backgrounds/water.ogv differ
diff --git a/res/bgm/firsthalf.flac b/res/bgm/firsthalf.flac
new file mode 100644
index 0000000..742e36f
Binary files /dev/null and b/res/bgm/firsthalf.flac differ
diff --git a/res/bgm/secondhalf.flac b/res/bgm/secondhalf.flac
new file mode 100644
index 0000000..37ae4c3
Binary files /dev/null and b/res/bgm/secondhalf.flac differ
diff --git a/res/bgm/title.flac b/res/bgm/title.flac
new file mode 100644
index 0000000..f5fd02a
Binary files /dev/null and b/res/bgm/title.flac differ
diff --git a/res/fonts/Iosevka-Bold.ttf b/res/fonts/Iosevka-Bold.ttf
new file mode 100644
index 0000000..d52863a
Binary files /dev/null and b/res/fonts/Iosevka-Bold.ttf differ
diff --git a/res/fonts/Iosevka-Heavy.ttf b/res/fonts/Iosevka-Heavy.ttf
new file mode 100644
index 0000000..e250613
Binary files /dev/null and b/res/fonts/Iosevka-Heavy.ttf differ
diff --git a/res/fonts/techmino_proportional.otf b/res/fonts/techmino_proportional.otf
new file mode 100644
index 0000000..a817a0b
Binary files /dev/null and b/res/fonts/techmino_proportional.otf differ
diff --git a/res/img/b.png b/res/img/b.png
new file mode 100644
index 0000000..84b9efa
Binary files /dev/null and b/res/img/b.png differ
diff --git a/res/img/b_d.png b/res/img/b_d.png
new file mode 100644
index 0000000..7230e74
Binary files /dev/null and b/res/img/b_d.png differ
diff --git a/res/img/bl.png b/res/img/bl.png
new file mode 100644
index 0000000..be7f0cc
Binary files /dev/null and b/res/img/bl.png differ
diff --git a/res/img/g.png b/res/img/g.png
new file mode 100644
index 0000000..503245e
Binary files /dev/null and b/res/img/g.png differ
diff --git a/res/img/g_d.png b/res/img/g_d.png
new file mode 100644
index 0000000..b2daad3
Binary files /dev/null and b/res/img/g_d.png differ
diff --git a/res/img/i.png b/res/img/i.png
new file mode 100644
index 0000000..f2e70bf
Binary files /dev/null and b/res/img/i.png differ
diff --git a/res/img/i_d.png b/res/img/i_d.png
new file mode 100644
index 0000000..03a28f7
Binary files /dev/null and b/res/img/i_d.png differ
diff --git a/res/img/o.png b/res/img/o.png
new file mode 100644
index 0000000..736fa8a
Binary files /dev/null and b/res/img/o.png differ
diff --git a/res/img/o_d.png b/res/img/o_d.png
new file mode 100644
index 0000000..9e75359
Binary files /dev/null and b/res/img/o_d.png differ
diff --git a/res/img/r.png b/res/img/r.png
new file mode 100644
index 0000000..ba724dc
Binary files /dev/null and b/res/img/r.png differ
diff --git a/res/img/r_d.png b/res/img/r_d.png
new file mode 100644
index 0000000..ca73a52
Binary files /dev/null and b/res/img/r_d.png differ
diff --git a/res/img/t.png b/res/img/t.png
new file mode 100644
index 0000000..6d3d184
Binary files /dev/null and b/res/img/t.png differ
diff --git a/res/img/v.png b/res/img/v.png
new file mode 100644
index 0000000..930b7ac
Binary files /dev/null and b/res/img/v.png differ
diff --git a/res/img/v_d.png b/res/img/v_d.png
new file mode 100644
index 0000000..371fc24
Binary files /dev/null and b/res/img/v_d.png differ
diff --git a/res/img/w.png b/res/img/w.png
new file mode 100644
index 0000000..b536f5d
Binary files /dev/null and b/res/img/w.png differ
diff --git a/res/img/y.png b/res/img/y.png
new file mode 100644
index 0000000..0a4de8d
Binary files /dev/null and b/res/img/y.png differ
diff --git a/res/img/y_d.png b/res/img/y_d.png
new file mode 100644
index 0000000..66d19af
Binary files /dev/null and b/res/img/y_d.png differ
diff --git a/res/loading.png b/res/loading.png
new file mode 100644
index 0000000..93aff83
Binary files /dev/null and b/res/loading.png differ
diff --git a/res/se/autopromote.wav b/res/se/autopromote.wav
new file mode 100644
index 0000000..d017fb9
Binary files /dev/null and b/res/se/autopromote.wav differ
diff --git a/res/se/bottom.wav b/res/se/bottom.wav
new file mode 100644
index 0000000..688accb
Binary files /dev/null and b/res/se/bottom.wav differ
diff --git a/res/se/demote.wav b/res/se/demote.wav
new file mode 100644
index 0000000..7cdc306
Binary files /dev/null and b/res/se/demote.wav differ
diff --git a/res/se/erase.wav b/res/se/erase.wav
new file mode 100644
index 0000000..da7f99b
Binary files /dev/null and b/res/se/erase.wav differ
diff --git a/res/se/fall.wav b/res/se/fall.wav
new file mode 100644
index 0000000..2ba1cd4
Binary files /dev/null and b/res/se/fall.wav differ
diff --git a/res/se/lock.wav b/res/se/lock.wav
new file mode 100644
index 0000000..c34682c
Binary files /dev/null and b/res/se/lock.wav differ
diff --git a/res/se/promote.wav b/res/se/promote.wav
new file mode 100644
index 0000000..8261581
Binary files /dev/null and b/res/se/promote.wav differ
diff --git a/res/se/ready.wav b/res/se/ready.wav
new file mode 100644
index 0000000..f38758d
Binary files /dev/null and b/res/se/ready.wav differ
diff --git a/scene/exit.lua b/scene/exit.lua
new file mode 100644
index 0000000..e3a1c20
--- /dev/null
+++ b/scene/exit.lua
@@ -0,0 +1,21 @@
+local ExitScene = SCENE:extend()
+ExitScene.title = "Exit Game"
+
+function ExitScene:new()
+end
+
+function ExitScene:update()
+ love.event.quit()
+end
+
+function ExitScene:render()
+end
+
+function ExitScene:changeOption(rel)
+end
+
+function ExitScene:onInputPress(e)
+end
+
+return ExitScene
+
diff --git a/scene/fullscreen.lua b/scene/fullscreen.lua
new file mode 100644
index 0000000..9188322
--- /dev/null
+++ b/scene/fullscreen.lua
@@ -0,0 +1,22 @@
+local FullscreenScene = SCENE:extend()
+FullscreenScene.title = "Fullscreen"
+
+function FullscreenScene:new()
+end
+
+function FullscreenScene:update()
+ SETTINGS["fullscreen"] = not SETTINGS["fullscreen"]
+ love.window.setFullscreen(SETTINGS["fullscreen"])
+ SCENE = TitleScene()
+end
+
+function FullscreenScene:render()
+end
+
+function FullscreenScene:changeOption(rel)
+end
+
+function FullscreenScene:onInputPress(e)
+end
+
+return FullscreenScene
diff --git a/scene/game.lua b/scene/game.lua
new file mode 100644
index 0000000..c42079a
--- /dev/null
+++ b/scene/game.lua
@@ -0,0 +1,188 @@
+local GameScene = SCENE:extend()
+GameScene.title = "Game"
+
+local tas = false
+
+-- 70 295
+local buttonList = {
+ BUTTON.new{
+ text = "Rotate Left\n Pause/Frame Step",
+ x = 67, y = 289, h = 40, w = 140,
+ backgroundColor = {0, 0, 0, 0},
+ borderColor = {0, 0, 0, 0},
+ hoverColor = { 1, 1, 1, 0.2},
+ textOrientation = "left",
+ codeWhenPressed = function() SCENE:onInputPress{input = "rotate_left"} end
+ },
+ BUTTON.new{
+ text = "Rotate Right\n Unpause",
+ x = 67, y = 323, h = 40, w = 140,
+ backgroundColor = {0, 0, 0, 0},
+ borderColor = {0, 0, 0, 0},
+ hoverColor = { 1, 1, 1, 0.2},
+ textOrientation = "left",
+ codeWhenPressed = function() SCENE:onInputPress{input = "rotate_right"} end
+ },
+ BUTTON.new{
+ text = "Left\n Rewind 5 sec",
+ x = 67, y = 357, h = 40, w = 140,
+ backgroundColor = {0, 0, 0, 0},
+ borderColor = {0, 0, 0, 0},
+ hoverColor = { 1, 1, 1, 0.2},
+ textOrientation = "left",
+ codeWhenPressed = function() SCENE:onInputPress{input = "left"} end
+ },
+ BUTTON.new{
+ text = "Right\n FF 10 sec",
+ x = 67, y = 391, h = 40, w = 140,
+ backgroundColor = {0, 0, 0, 0},
+ borderColor = {0, 0, 0, 0},
+ hoverColor = { 1, 1, 1, 0.2},
+ textOrientation = "left",
+ codeWhenPressed = function() SCENE:onInputPress{input = "right"} end
+ },
+}
+local menuKey -- MENU key used to go main menu XD
+
+function GameScene:new(player_name, replay_file, replay_grade)
+ VCTRL[9].show = false
+
+ menuKey = BUTTON.new{
+ text = "MENU",
+ x = 265, y = 0, w = 60, h = 25,
+ codeWhenReleased = function()
+ if self.game.input_playback or self.game.game_over or self.game.completed then
+ SCENE = TitleScene()
+ end
+ end
+ }
+
+ game_mode = require 'game.gamemode'
+ if PENTO_MODE then
+ ruleset = require 'game.rotation_pent'
+ else
+ ruleset = require 'game.rotation'
+ end
+
+ self.retry_mode = game_mode
+ self.retry_ruleset = ruleset
+ -- self.secret_inputs = inputs
+ self.reset_stuff = {player_name, replay_file, replay_grade}
+ self.game = game_mode(player_name, replay_file, replay_grade)
+ self.ruleset = ruleset(self.game)
+ self.grace_frames = 0
+ self.normal_volume = love.audio.getVolume()
+ self.game:initialize(self.ruleset)
+ self.inputs = {
+ left=false,
+ right=false,
+ up=false,
+ down=false,
+ rotate_left=false,
+ rotate_left2=false,
+ rotate_right=false,
+ rotate_right2=false,
+ hold=false,
+ }
+ self.paused = false
+end
+
+function GameScene:update(nosound, tas_update)
+ local inputs = {}
+ if tas then
+ while self.game.are > 2 do
+ self.game:update(inputs, self.ruleset)
+ end
+ end
+ for input, value in pairs(self.inputs) do
+ inputs[input] = value
+ end
+ if tas and tas_update then
+ self.paused = false
+ self.game:update(inputs, self.ruleset)
+ self.paused = true
+ return
+ end
+ if nosound then
+ love.audio.setVolume(0)
+ end
+ if not nosound and self.grace_frames > 0 then
+ self.grace_frames = self.grace_frames - 1
+ if self.grace_frames == 1 then love.audio.setVolume(self.normal_volume) end
+ end
+ if not self.paused then
+ self.game:update(inputs, self.ruleset)
+ end
+ -- if self.game.input_playback or self.game.game_over or self.game.game_completed then
+ -- VCTRL[9].show = true; VCTRL[10].show = true
+ -- end
+end
+
+function GameScene:render()
+ self.game:draw(self.paused)
+ if self.game.input_playback then
+ BUTTON.draw(buttonList)
+ else
+ VCTRL.draw()
+ end
+ if self.game.input_playback or self.game.game_over or self.game.completed then
+ menuKey:draw()
+ end
+end
+
+function GameScene:onInputPress(e)
+ if e.type == "mouse" or (e.type == "touch" and not VCTRL.press(e.x, e.y, e.id)) then
+ if self.game.input_playback then BUTTON.press(buttonList, e.x, e.y, e.id) end
+ menuKey:press(e.x, e.y, e.id)
+ elseif (self.game.game_over or self.game.completed) and (e.input == "menu_decide" or e.input == "menu_back" or e.input == "rotate_right") and self.game.game_over_frames > 50 then
+ SCENE = TitleScene()
+ elseif tas and e.input == "menu_decide" then
+ self:update(false, true)
+ elseif self.game.input_playback and (e.input == "menu_back") then
+ SCENE = TitleScene()
+ elseif self.game.input_playback and e.input == "rotate_left" then
+ self.paused = false
+ self:update()
+ self.paused = true
+ elseif self.game.input_playback and e.input == "rotate_right" then
+ self.paused = false
+ elseif self.game.input_playback and not self.paused and e.input == 'left' then
+ local target = self.game.frames - 300
+ if target < 1 then target = 1 end
+ self.game = game_mode(self.reset_stuff[1], self.reset_stuff[2], self.reset_stuff[3])
+ self.ruleset = ruleset(self.game)
+ self.game:initialize(self.ruleset)
+ while self.game.frames < (target) do
+ self:update(true)
+ end
+ self.grace_frames = 90
+ elseif self.game.input_playback and not self.paused and e.input == 'right' then
+ local target = self.game.frames + 600
+ if target > #self.game.replay_inputs then target = #self.game.replay_inputs-10 end
+ -- self.game = game_mode(self.reset_stuff[1], self.reset_stuff[2], self.reset_stuff[3])
+ self.ruleset = ruleset(self.game)
+ self.game:initialize(self.ruleset)
+ while self.game.frames < (target) do
+ self:update(true)
+ end
+ self.grace_frames = 90
+ elseif e.input and string.sub(e.input, 1, 5) ~= "menu_" then
+ self.inputs[e.input] = true
+ end
+end
+
+function GameScene:onInputRelease(e)
+ if e.type == "mouse" or (e.type == "touch" and not VCTRL.release(e.id)) then
+ if self.game.input_playback then BUTTON.release(buttonList, e.x, e.y, e.id) end
+ menuKey:release(e.x, e.y, e.id)
+ elseif e.input and string.sub(e.input, 1, 5) ~= "menu_" then
+ self.inputs[e.input] = false
+ end
+end
+
+function GameScene:onInputMove(e)
+ if e.type == "mouse" then BUTTON.checkHovering(buttonList, e.x, e.y) end
+ menuKey._hovering = menuKey:isHovering(e.x, e.y)
+end
+
+return GameScene
diff --git a/scene/input_config.lua b/scene/input_config.lua
new file mode 100644
index 0000000..974d570
--- /dev/null
+++ b/scene/input_config.lua
@@ -0,0 +1,194 @@
+local ConfigScene = SCENE:extend()
+ConfigScene.title = "Input Config"
+
+local menu_screens = {
+ KeyConfigScene,
+ StickConfigScene,
+ TouchConfigScene,
+}
+
+local buttonList
+local function updateButtonList(self)
+ buttonList = {
+ BUTTON.new{
+ text = "1", font = FONT_big,
+ x = 75, y = 160, w = 40, h = 40,
+ codeWhenReleased = function()
+ if self.menu_state ~= 1 then
+ self.menu_state = 1
+ else
+ SCENE = KeyConfigScene()
+ end
+ end
+ },
+ BUTTON.new{
+ text = "2", font = FONT_big,
+ x = 75, y = 200, w = 40, h = 40,
+ codeWhenReleased = function()
+ if self.menu_state ~= 2 then
+ self.menu_state = 2
+ else
+ SCENE = StickConfigScene()
+ end
+ end
+ },
+ BUTTON.new{
+ text = "3", font = FONT_big,
+ x = 75, y = 240, w = 40, h = 40,
+ codeWhenReleased = function()
+ if self.menu_state ~= 3 then
+ self.menu_state = 3
+ else
+ SCENE = TouchConfigScene()
+ end
+ end
+ }
+ }
+ if not SETTINGS.firstTime then
+ menu_screens[4] = TitleScene
+ buttonList[4] = BUTTON.new{
+ text = CHAR.icon.home, font = FONT_big,
+ x = 75, y = 280, w = 40, h = 40,
+ codeWhenReleased = function()
+ if self.menu_state ~= 4 then
+ self.menu_state = 4
+ else
+ SCENE = TitleScene()
+ end
+ end
+ }
+ else
+ menu_screens[4] = nil
+ buttonList[4] = nil
+ end
+end
+
+local secret_code_input = {}
+local secret_code_used = false
+
+function ConfigScene:new(first_time)
+ updateButtonList(self)
+
+ secret_code_used = false
+ secret_code_input = {} -- When it matches 88663366 then we will automatically set the special keybind
+
+ self.menu_state = 1
+ if first_time then
+ self.first_time = true
+ else
+ self.first_time = false
+ end
+end
+
+function ConfigScene:update() end
+
+function ConfigScene:render()
+ MainBackground()
+ if secret_code_used then
+ if SETTINGS.tvMode then
+ drawText("TV mode is ON now! Check keybind below", 80, 40, 1000)
+ drawText("Which controls do you want to configure?", 80, 70, 1000)
+ drawText(
+ "2 - Up 1 - Rotate left 5 - Confirm selection\n"..
+ "8 - Right 3 - Rotate right 0 - Back\n"..
+ "4 - Left 7 - Rotate left 2\n"..
+ "6 - Down 9 - Rotate right 2", 80, 350, 1000
+ )
+ else
+ drawText("TV mode is OFF now!", 80, 40, 1000)
+ drawText("Which controls do you want to configure?", 80, 70, 1000)
+ end
+ elseif self.first_time then
+ drawText("Thanks for playing Tromi!", 80, 40, 1000)
+ drawText("Please begin by configuring your controls:", 80, 70, 1000)
+ else
+ drawText("Which controls do you want to configure?", 80, 70, 1000)
+ end
+ drawBigText(table.concat(secret_code_input, " "), 80, 100, 1000)
+
+ love.graphics.setColor(1, 1, 1, 0.5)
+ love.graphics.rectangle("fill", 75, 120 + 40 * self.menu_state, 300, 40)
+
+ love.graphics.setColor(1, 1, 1, 1)
+ for i, screen in pairs(menu_screens) do
+ drawText(screen.title, 130, 130 + 40 * i, 300, "left")
+ end
+ BUTTON.draw(buttonList)
+end
+
+function ConfigScene:changeOption(rel)
+ local len = #menu_screens
+ self.menu_state = (self.menu_state + len + rel - 1) % len + 1
+end
+
+function ConfigScene:onInputMove(e)
+ if e.type == "mouse" then
+ BUTTON.checkHovering(buttonList, e.x, e.y)
+ end
+end
+
+---@param key string
+local function checkSecretCodeInput(key)
+ if secret_code_used then return end
+ if key:sub(1, 2) == "kp" then
+ table.insert(secret_code_input, key:sub(3,3))
+ elseif key:find("[0-9.]") == 1 then
+ table.insert(secret_code_input, key)
+ else
+ secret_code_input = {} -- Reset
+ end
+
+ if #secret_code_input > 8 then
+ table.remove(secret_code_input, 1)
+ end
+
+ local current_code = table.concat(secret_code_input, "")
+ if current_code == "88663366" then --TVMODEON
+ -- Set keybind
+ SETTINGS.input.keys = {
+ ["2"] = "up", ["kp2"] = "up",
+ ["8"] = "down", ["kp8"] = "down",
+ ["4"] = "left", ["kp4"] = "left",
+ ["6"] = "right", ["kp6"] = "right",
+ ["1"] = "rotate_left", ["kp1"] = "rotate_left",
+ ["3"] = "rotate_right", ["kp3"] = "rotate_right",
+ ["7"] = "rotate_left2", ["kp7"] = "rotate_left2",
+ ["9"] = "rotate_right2", ["kp9"] = "rotate_right2",
+ ["5"] = "menu_decide", ["kp5"] = "menu_decide",
+ ["0"] = "menu_back", ["kp0"] = "menu_back",
+ }
+ SETTINGS.firstTime = false
+ SETTINGS.tvMode = true
+ secret_code_used = true
+ updateButtonList(SCENE)
+ elseif current_code == "........" then
+ SETTINGS.input.keys = {}
+ SETTINGS.tvMode = false
+ secret_code_used = true
+ end
+end
+
+---@param e SCENE_onInput
+function ConfigScene:onInputPress(e)
+ if e.type == "touch" or e.type == "mouse" then
+ BUTTON.press(buttonList, e.x, e.y, e.id)
+ elseif e.input == "menu_decide" or e.input == "rotate_left" or e.scancode == "return" then
+ SCENE = menu_screens[self.menu_state]()
+ elseif e.input == "up" or e.scancode == "up" then
+ self:changeOption(-1)
+ elseif e.input == "down" or e.scancode == "down" then
+ self:changeOption(1)
+ elseif not SETTINGS.firstTime and (
+ e.input == "menu_back" or e.input == "rotate_right" or e.scancode == "backspace" or e.scancode == "delete"
+ ) then
+ SCENE = TitleScene()
+ end
+ checkSecretCodeInput(e.key or "")
+end
+function ConfigScene:onInputRelease(e)
+ if e.type == "touch" or e.type == "mouse" then
+ BUTTON.release(buttonList, e.x, e.y, e.id)
+ end
+end
+
+return ConfigScene
diff --git a/scene/key_config.lua b/scene/key_config.lua
new file mode 100644
index 0000000..4d87f67
--- /dev/null
+++ b/scene/key_config.lua
@@ -0,0 +1,95 @@
+local KeyConfigScene = SCENE:extend()
+KeyConfigScene.title = "Key Config"
+
+local configurable_inputs = {
+ "menu_decide",
+ "menu_back",
+ "left",
+ "right",
+ "up",
+ "down",
+ "rotate_left",
+ "rotate_left2",
+ "rotate_right",
+ "rotate_right2",
+}
+
+local input_names = {
+ menu_decide='Confirm Selection',
+ menu_back = 'Go Back',
+ left='Left',
+ right='Right',
+ up='Up',
+ down='Down',
+ rotate_left='Rotate Counter-clockwise',
+ rotate_left2='Rotate Counter-clockwise (2)',
+ rotate_right='Rotate Clockwise',
+ rotate_right2='Rotate Clockwise (2)'
+}
+
+
+local function newSetInputs()
+ local set_inputs = {}
+ for i, input in ipairs(configurable_inputs) do
+ set_inputs[input] = false
+ end
+ return set_inputs
+end
+
+function KeyConfigScene:new()
+ self.input_state = 1
+ self.set_inputs = newSetInputs()
+ self.new_input = {}
+end
+
+function KeyConfigScene:update()
+end
+
+function KeyConfigScene:render()
+ MainBackground()
+ for i, input in ipairs(configurable_inputs) do
+ drawText(input_names[input], 40, 50 + i * 20, 200, "left")
+ if self.set_inputs[input] then
+ drawText(self.set_inputs[input], 240, 50 + i * 20, 300, "left")
+ end
+ end
+ if self.input_state > #configurable_inputs then
+ drawText("Press Enter/Confirm Selection to confirm, delete/backspace to retry" .. (SETTINGS.input and ", escape/Go Back to cancel" or ""),0,0,1000)
+ else
+ drawText("Press key input for " .. input_names[configurable_inputs[self.input_state]] .. ", tab to skip, escape to cancel",0,0,1000)
+ drawText("Press any key from other input than keyboard will also skip.\nFunction keys (F1, F2, etc.), escape, and tab can't be changed", 0, 20,1000)
+ end
+end
+
+function KeyConfigScene:onInputPress(e)
+ if e.type == "key" then
+ -- function keys, escape, and tab are reserved and can't be remapped
+ if e.scancode == "escape" or (self.input_state > #configurable_inputs and e.input == "menu_back") then
+ SCENE = InputConfigScene(SETTINGS.firstTime)
+ elseif self.input_state > #configurable_inputs then
+ if e.scancode == "return" or e.input == "menu_decide" then
+ SETTINGS.input.keys = self.new_input
+ SCENE = SETTINGS.firstTime and TitleScene() or InputConfigScene()
+ SETTINGS.firstTime = false
+ elseif e.scancode == "delete" or e.scancode == "backspace" then
+ -- retry
+ self.input_state = 1
+ self.set_inputs = newSetInputs()
+ self.new_input = {}
+ end
+ elseif e.scancode == "tab" then
+ self.set_inputs[configurable_inputs[self.input_state]] = "skipped"
+ self.input_state = self.input_state + 1
+ elseif not self.new_input[e.scancode] then
+ -- all other keys can be configured
+ self.set_inputs[configurable_inputs[self.input_state]] = "key " .. love.keyboard.getKeyFromScancode(e.scancode) .. " (" .. e.scancode .. ")"
+ self.new_input[e.scancode] = configurable_inputs[self.input_state]
+ self.input_state = self.input_state + 1
+ end
+ elseif self.input_state < #configurable_inputs then
+ self.set_inputs[configurable_inputs[self.input_state]] = "skipped"
+ self.input_state = self.input_state + 1
+ end
+end
+
+return KeyConfigScene
diff --git a/scene/lines_toggle.lua b/scene/lines_toggle.lua
new file mode 100644
index 0000000..06aa01b
--- /dev/null
+++ b/scene/lines_toggle.lua
@@ -0,0 +1,21 @@
+local LinesToggleScene = SCENE:extend()
+LinesToggleScene.title = "Show lines during game:"
+
+function LinesToggleScene:new()
+end
+
+function LinesToggleScene:update()
+ SETTINGS["lines"] = not SETTINGS["lines"]
+ SCENE = TitleScene()
+end
+
+function LinesToggleScene:render()
+end
+
+function LinesToggleScene:changeOption(rel)
+end
+
+function LinesToggleScene:onInputPress(e)
+end
+
+return LinesToggleScene
\ No newline at end of file
diff --git a/scene/music_toggle.lua b/scene/music_toggle.lua
new file mode 100644
index 0000000..0f6e5d8
--- /dev/null
+++ b/scene/music_toggle.lua
@@ -0,0 +1,21 @@
+local MusicToggleScene = SCENE:extend()
+MusicToggleScene.title = "Play music during game:"
+
+function MusicToggleScene:new()
+end
+
+function MusicToggleScene:update()
+ SETTINGS["music"] = not SETTINGS["music"]
+ SCENE = TitleScene()
+end
+
+function MusicToggleScene:render()
+end
+
+function MusicToggleScene:changeOption(rel)
+end
+
+function MusicToggleScene:onInputPress(e)
+end
+
+return MusicToggleScene
diff --git a/scene/name_entry.lua b/scene/name_entry.lua
new file mode 100644
index 0000000..e35aa4d
--- /dev/null
+++ b/scene/name_entry.lua
@@ -0,0 +1,192 @@
+local NameEntryScene = SCENE:extend()
+NameEntryScene.title = "Game Start"
+
+local buttonList = {
+ BUTTON.new{
+ text = "←\nCHAR", font = FONT_big,
+ x = 25, y = 120, w = 80, h = 80,
+ codeWhenPressed = function() SCENE:onInputPress {input = "left"} end,
+ codeWhenReleased = function() SCENE:onInputRelease{input = "left"} end,
+ },
+ BUTTON.new{
+ text = "→\nCHAR", font = FONT_big,
+ x = 115, y = 120, w = 80, h = 80,
+ codeWhenPressed = function() SCENE:onInputPress {input = "right"} end,
+ codeWhenReleased = function() SCENE:onInputRelease{input = "right"} end,
+ },
+ BUTTON.new{
+ text = "↑\nESC", font = FONT_big,
+ x = 25, y = 210, w = 80, h = 80,
+ codeWhenPressed = function() SCENE:onInputPress {input = "menu_back"} end,
+ codeWhenReleased = function() SCENE:onInputRelease{input = "menu_back"} end,
+ },
+ BUTTON.new{
+ text = "↓\nENTER", font = FONT_big,
+ x = 115, y = 210, w = 80, h = 80,
+ codeWhenPressed = function() SCENE:onInputPress {input = "menu_decide"} end,
+ codeWhenReleased = function() SCENE:onInputRelease{input = "menu_decide"} end,
+ },
+}
+
+local Grid = require 'game.grid'
+local bitser = require 'libs.bitser'
+
+function NameEntryScene:new()
+ VCTRL.toggle(false)
+
+ self.chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890."
+ self.char_pos = 1
+ self.name_entry = {'A','A','A'}
+ self.entry_pos = 1
+ self.entry_chars = self.name_entry[1]..self.name_entry[2]..self.name_entry[3]
+ self.grid = Grid(10, 20)
+ self.repeat_limit = 10
+ self.repeat_counter = self.repeat_limit-1
+ self.direction = nil
+ self.grade = 0
+ self.wins = 0
+ self.plays = 0
+ self.gradeNames = {
+ "19k", "18k", "17k", "16k", "15k", "14k", "13k", "12k", "11k",
+ "10k", "9k", "8k", "7k", "6k", "5k", "4k", "3k", "2k", "1k",
+ "1D", "2D", "3D", "4D", "5D", "6D", "7D", "8D", "9D"
+ }
+ if SETTINGS['last_entry'] ~= nil then
+ self.name_entry = {SETTINGS['last_entry']:sub(1,1),SETTINGS['last_entry']:sub(2,2),SETTINGS['last_entry']:sub(3,3)}
+ self.entry_pos = 3
+ end
+ if love.filesystem.getInfo(HIscoreFILE) then
+ self.hi_scores = FILE.read(HIscoreFILE)
+ else
+ self.hi_scores = {"TRO",0,"MIT",0,"ROM",0,"ITR",0,"OMI",0}
+ end
+end
+
+function NameEntryScene:drawGradeList(left, top)
+ love.graphics.setColor(0,0,0,0.5)
+ love.graphics.rectangle("fill", left+3, top+3, 200, 240, 10, 10)
+ love.graphics.setColor(0.05,0.05,0.05,1)
+ love.graphics.rectangle("fill", left, top, 200, 240, 10, 10)
+ drawText("Grade list:", left+15, top+10, 1000, "left")
+ drawText("Beginner\n19 kyu\n18 kyu\n17 kyu\n16 kyu\n15 kyu\n14 kyu\n13 kyu\n12 kyu\n11 kyu\n10 kyu", left+15, top+25, 1000, "left")
+ drawText("Intermed.\n9 kyu\n8 kyu\n7 kyu\n6 kyu\n5 kyu\n4 kyu\n3 kyu\n2 kyu\n1 kyu", left+80, top+25, 1000, "left")
+ drawText("Expert\n1 Dan\n2 Dan\n3 Dan\n4 Dan\n5 Dan\n6 Dan\n7 Dan\n8 Dan\n9 Dan", left+145, top+25, 1000, "left")
+end
+
+function NameEntryScene:render()
+ MainBackground()
+ BUTTON.draw(buttonList)
+
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.line(216,80,216,80+(16*self.grid.height))
+ love.graphics.line(216+(16*self.grid.width),80,216+(16*self.grid.width),80+(16*self.grid.height))
+ love.graphics.line(216,80+(16*self.grid.height),216+(16*self.grid.width),80+(16*self.grid.height))
+ love.graphics.line(216,80,216+(16*self.grid.width),80)
+ love.graphics.setColor(0, 0, 0, 1)
+ love.graphics.rectangle(
+ "fill", 216, 80,
+ 16 * self.grid.width, 16 * self.grid.height
+ )
+ love.graphics.setColor(1, 1, 1, 1)
+ drawText('Enter your initials:', 227, 180, 200, "left")
+ drawBigText(self.entry_chars, 272, 200, 200, "left")
+ drawText('o', 262+(self.entry_pos*14), 225, 200, "left")
+ self:drawGradeList(397, 40)
+ love.graphics.setColor(0,0,0,0.5)
+ love.graphics.rectangle("fill", 400, 295, 130, 130, 10, 10)
+ love.graphics.setColor(0.05,0.05,0.05,1)
+ love.graphics.rectangle("fill", 397, 292, 130, 130, 10, 10)
+ drawText("Best scores:", 410, 297, 1000, "left")
+ i = 2
+ while i <= 10 do
+ drawText(self.hi_scores[i-1]..' - '..self.hi_scores[i], 410, 297+(i*10), 1000, "left")
+ i = i + 2
+ end
+ if self.entry_pos == 4 then
+ drawText('Press confirm\nto play', 255, 290, 1000)
+ end
+ if self.grade > 0 then
+ drawText(string.format('Games: %s', self.plays), 255, 250, 1000)
+ drawText(string.format('Grade: %s', self.gradeNames[self.grade]), 255, 270, 1000)
+ end
+end
+
+function NameEntryScene:update()
+ if self.direction == "left" then
+ if self.repeat_counter >= self.repeat_limit then
+ self.char_pos = self.char_pos - 1
+ if self.char_pos < 1 then self.char_pos = 37 end
+ self.name_entry[self.entry_pos] = self.chars:sub(self.char_pos, self.char_pos)
+ self.repeat_counter = 0
+ end
+ self.repeat_counter = self.repeat_counter + 1
+ elseif self.direction == "right" then
+ if self.repeat_counter >= self.repeat_limit then
+ self.char_pos = self.char_pos + 1
+ if self.char_pos > 37 then self.char_pos = 1 end
+ self.name_entry[self.entry_pos] = self.chars:sub(self.char_pos, self.char_pos)
+ self.repeat_counter = 0
+ end
+ self.repeat_counter = self.repeat_counter + 1
+ end
+ self.entry_chars = self.name_entry[1]..self.name_entry[2]..self.name_entry[3]
+end
+
+function NameEntryScene:onInputMove(e)
+ if e.type == "mouse" then
+ BUTTON.checkHovering(buttonList, e.x, e.y)
+ end
+end
+
+function NameEntryScene:onInputPress(e)
+ if e.type == "mouse" or e.type == "touch" then
+ BUTTON.press(buttonList, e.x, e.y, e.id)
+ elseif e.input == "menu_decide" or e.input == "rotate_left" or e.scancode == "return" then
+ if self.entry_pos == 4 then
+ BUTTON.release(buttonList, e.x, e.y, e.id)
+ SETTINGS['last_entry'] = name:upper()
+ SCENE = GameScene(name:lower())
+ else
+ if self.entry_pos == 3 then
+ name = string.lower(self.name_entry[1]..self.name_entry[2]..self.name_entry[3])
+ if love.filesystem.getInfo((SAVE_DIR..name.."_grade_history.sav")) then
+ grade_history = FILE.read(SAVE_DIR..name.."_grade_history.sav")
+ self.grade = grade_history[1]
+ self.wins = grade_history[2]
+ self.plays = grade_history[4]
+ end
+ end
+ if self.entry_pos < 3 then
+ self.name_entry[self.entry_pos] = self.chars:sub(self.char_pos, self.char_pos)
+ self.name_entry[self.entry_pos+1] = self.chars:sub(self.char_pos, self.char_pos)
+ end
+ self.entry_pos = self.entry_pos + 1
+ end
+ elseif e.input == "left" or e.scancode == "left" then
+ self.direction = "left"
+ elseif e.input == "right" or e.scancode == "right" then
+ self.direction = "right"
+ elseif e.input == "menu_back" or e.input == "rotate_right" or e.scancode == "delete" or e.scancode == "backspace" then
+ if self.entry_pos == 1 then
+ BUTTON.release(buttonList, true)
+ SCENE = TitleScene()
+ else
+ self.name_entry[self.entry_pos] = 'A'
+ self.name_entry[self.entry_pos-1] = 'A'
+ self.char_pos = 1
+ self.entry_pos = self.entry_pos - 1
+ self.grade = 0
+ end
+ end
+end
+
+function NameEntryScene:onInputRelease(e)
+ if e.type == "mouse" or e.type == "touch" then
+ BUTTON.release(buttonList, e.x, e.y, e.id)
+ elseif e.input == "left" or e.scancode == "left" or e.input == "right" or e.scancode == "right" then
+ self.direction = nil
+ self.repeat_counter = self.repeat_limit-1
+ end
+end
+
+return NameEntryScene
diff --git a/scene/replay.lua b/scene/replay.lua
new file mode 100644
index 0000000..2a4e6de
--- /dev/null
+++ b/scene/replay.lua
@@ -0,0 +1,204 @@
+local ReplaySelectScene = SCENE:extend()
+ReplaySelectScene.title = "Replay"
+
+local replay_list
+local buttonList = {
+ BUTTON.new{
+ text = CHAR.key.up.."\nUP", font = FONT_big,
+ x = 425, y = 80, w = 80, h = 80,
+ codeWhenPressed = function() SCENE:onInputPress {input = "up"} end,
+ codeWhenReleased = function() SCENE:onInputRelease{input = "up"} end
+ },
+ BUTTON.new{
+ text = CHAR.key.down.."\nDOWN", font = FONT_big,
+ x = 425, y = 240, w = 80, h = 80,
+ codeWhenPressed = function() SCENE:onInputPress {input = "down"} end,
+ codeWhenReleased = function() SCENE:onInputRelease{input = "down"} end
+ },
+ BUTTON.new{
+ text = CHAR.icon.play.."\nPLAY", font = FONT_big,
+ x = 345, y = 160, w = 80, h = 80,
+ codeWhenPressed = function() SCENE:onInputPress {input = "menu_decide"} end,
+ codeWhenReleased = function() SCENE:onInputRelease{input = "menu_decide"} end
+ },
+ BUTTON.new{
+ text = CHAR.icon.home.."\nHOME", font = FONT_big,
+ x = 505, y = 160, w = 80, h = 80,
+ codeWhenPressed = function() SCENE:onInputPress {input = "menu_back"} end,
+ codeWhenReleased = function() SCENE:onInputRelease{input = "menu_back"} end
+ },
+ BUTTON.new{
+ text = CHAR.icon.export.."\nEXP.", font = FONT_big,
+ x = 345, y = 320, w = 80, h = 80,
+ codeWhenPressed = function() SCENE:onInputPress {input = "rotate_left"} end,
+ codeWhenReleased = function() SCENE:onInputRelease{input = "rotate_left"} end
+ },
+ BUTTON.new{
+ text = CHAR.icon.import.."\nIMP.", font = FONT_big,
+ x = 505, y = 320, w = 80, h = 80,
+ codeWhenPressed = function() SCENE:onInputPress {input = "rotate_right"} end,
+ codeWhenReleased = function() SCENE:onInputRelease{input = "rotate_right"} end
+ },
+}
+
+function ReplaySelectScene:new()
+ self:initList()
+ PENTO_MODE = false
+end
+
+function ReplaySelectScene:initList()
+ self.replays = {}
+ replay_list = love.filesystem.getDirectoryItems('saves/replays/')
+ table.sort(replay_list, function(a,b) return a > b end)
+ self.replay_text = {}
+ self.page_flip = 16
+ self.page = 1
+ local gradeNames = {
+ "19k", "18k", "17k", "16k", "15k", "14k", "13k", "12k", "11k",
+ "10k", "9k", "8k", "7k", "6k", "5k", "4k", "3k", "2k", "1k",
+ "1D", "2D", "3D", "4D", "5D", "6D", "7D", "8D", "9D"
+ }
+ for i=1, #replay_list do
+ if string.find(replay_list[i], "_replay.sav") ~= nil then
+ table.insert(self.replays, replay_list[i])
+ local line_components = {}
+ for str in string.gmatch(replay_list[i], "([^".."_".."]+)") do
+ table.insert(line_components, str)
+ end
+ local player_name = line_components[2]
+ local player_grade = gradeNames[tonumber(line_components[3])]
+ local player_score = line_components[4]
+ if #player_grade == 2 then player_grade = ' '..player_grade end
+ table.insert(self.replay_text, player_name..' - '..player_grade..' - '..string.format('%6d',player_score))
+ end
+ end
+ self.replay_select = 1
+ self.direction = nil
+ self.repeat_limit = 10
+ self.repeat_counter = self.repeat_limit-1
+end
+
+function ReplaySelectScene:render()
+ MainBackground()
+
+ love.graphics.setColor(0.4, 1, 1, 0.5)
+ love.graphics.rectangle("fill", 0, 20 + 20 * self.replay_select, 640, 22)
+
+ BUTTON.draw(buttonList)
+
+ drawText('Name - Grade - Score', 40, 20, 1000, "left")
+ drawText(string.format('Page %s/%s', self.page, math.floor(#self.replays / self.page_flip) + 1), 20, 440, 1000, "left")
+
+ local i, j = 1, 1
+ while i <= #self.replay_text do
+ if j > self.page_flip then j = 1
+ elseif j < 1 then j = self.page_flip
+ end
+ if i > (self.page-1) * self.page_flip and i <= self.page * self.page_flip then
+ drawText(self.replay_text[i], 40, 20 + 20 * j, 1000, "left")
+ end
+ j = j + 1
+ i = i + 1
+ end
+
+ if self.replays[1] == nil then
+ drawText('No replays yet!', 40, 40, 1000, "left")
+ end
+end
+
+function ReplaySelectScene:update()
+ if self.direction == "up" then
+ if self.repeat_counter >= self.repeat_limit then
+ self:changeOption(-1)
+ self.repeat_counter = 0
+ end
+ self.repeat_counter = self.repeat_counter + 1
+ elseif self.direction == "down" then
+ if self.repeat_counter >= self.repeat_limit then
+ self:changeOption(1)
+ self.repeat_counter = 0
+ end
+ self.repeat_counter = self.repeat_counter + 1
+ end
+end
+
+function ReplaySelectScene:changeOption(rel)
+ local len = #self.replays
+ self.replay_select = self.replay_select + rel
+ if self.replay_select + ((self.page-1) * self.page_flip) > len then
+ self.page = 1
+ self.replay_select = 1
+ elseif self.replay_select < 1 and self.page == 1 then
+ self.page = 1+(math.floor(len / self.page_flip))
+ self.replay_select = len - ((self.page-1) * self.page_flip)
+ end
+ if self.replay_select > self.page_flip then
+ self.page = self.page + 1
+ self.replay_select = 1
+ elseif self.replay_select < 1 then
+ self.page = self.page - 1
+ self.replay_select = self.page_flip
+ end
+end
+
+function ReplaySelectScene:onInputPress(e)
+ local selected_replay = self.replays[self.replay_select + ((self.page-1) * self.page_flip)]
+ local selected_replay_text = self.replay_text[self.replay_select + ((self.page-1) * self.page_flip)]
+
+ if e.type == "touch" or e.type == "mouse" then
+ BUTTON.press(buttonList, e.x, e.y, e.id)
+ elseif e.input == "menu_decide" or e.scancode == "return" then
+ if self.replays[1] == nil then SCENE = TitleScene(); return
+ else
+ local line_components = {}
+ for str in string.gmatch(selected_replay_text, "([^".."-".."]+)") do
+ table.insert(line_components, str)
+ end
+ local player_name = line_components[1]
+ local player_grade = string.gsub(line_components[2], " ", "")
+ SCENE = GameScene(player_name, selected_replay, player_grade)
+ end
+ elseif e.input == "rotate_left" then -- Export
+ local plain_replay_data = love.data.encode("string", "base64", love.filesystem.read(REPLAY_DIR..selected_replay))
+ love.system.setClipboardText(("BEGINNING_OF_TROMI_REPLAY|%s|%s|END_OF_TROMI_REPLAY"):format(selected_replay, plain_replay_data))
+ elseif e.input == "rotate_right" then -- Import
+ local input_data = love.system.getClipboardText()
+ if input_data:find("BEGINNING_OF_TROMI_REPLAY|", 1, true) == 1 and input_data:find("|END_OF_TROMI_REPLAY", 1, true) == #input_data - 19 then
+ local data_part = input_data:sub(27, #input_data - 20)
+ local separator_pos = data_part:find("|", 1, true)
+ local replay_name = data_part:sub(1, separator_pos - 1)
+ local replay_path = REPLAY_DIR..replay_name
+ local replay_data = love.data.decode("string", "base64", data_part:sub(separator_pos + 1))
+ love.filesystem.write(replay_path, replay_data)
+
+ SCENE = ReplayTestScene(replay_name)
+ else
+ SCENE = ReplayTestScene()
+ end
+ elseif e.input == "up" or e.scancode == "up" then
+ if self.replays[1] == nil then SCENE = TitleScene(); return end
+ self.direction = 'up'
+ elseif e.input == "down" or e.scancode == "down" then
+ if self.replays[1] == nil then SCENE = TitleScene(); return end
+ self.direction = 'down'
+ elseif e.input == "menu_back" or e.scancode == "backspace" or e.scancode == "delete" then
+ SCENE = TitleScene()
+ end
+end
+
+function ReplaySelectScene:onInputRelease(e)
+ if e.type == "touch" or e.type == "mouse" then
+ BUTTON.release(buttonList, e.x, e.y, e.id)
+ elseif e.input == "up" or e.scancode == "up" or e.input == "down" or e.scancode == "down" then
+ self.direction = nil
+ self.repeat_counter = self.repeat_limit-1
+ end
+end
+
+function ReplaySelectScene:onInputMove(e)
+ if e.type == "mouse" then
+ BUTTON.checkHovering(buttonList, e.x, e.y)
+ end
+end
+
+return ReplaySelectScene
diff --git a/scene/replay_test.lua b/scene/replay_test.lua
new file mode 100644
index 0000000..00ae5ee
--- /dev/null
+++ b/scene/replay_test.lua
@@ -0,0 +1,51 @@
+local ReplayTestScene = SCENE:extend()
+
+local GAME
+local error_message
+local valid_data
+
+
+function ReplayTestScene:new(input_file)
+ if not input_file then
+ valid_data = false
+ return
+ else
+ valid_data = true
+ end
+ self.input_file = input_file
+
+ GAME = require("game.gamemode")
+ local okay, err = pcall(function()
+ GAME:new("TRO", input_file, "19k")
+ GAME:initialize(require"game.rotation")
+ end)
+ if not okay then
+ -- TODO
+ error_message = err
+ love.filesystem.remove(REPLAY_DIR..self.input_file)
+ end
+end
+
+function ReplayTestScene:render()
+ MainBackground()
+ if valid_data then
+ if error_message then
+ drawText("Replay test failed! Data corrupted!", 80, 40, 1000)
+ drawText("Press any key to go back. Anyway here is the error info:", 80, 70, 1000)
+ drawText(error_message, 80, 100, 560)
+ else
+ drawText("Replay test finished!", 80, 40, 1000)
+ drawText("Your replay was imported. Press any key to go back.", 80, 70, 1000)
+ end
+ else
+ drawText("Replay test failed! Not Tromi's replay data", 80, 40, 1000)
+ drawText("Press any key to go back, and check your device's clipboard again!", 80, 100, 1000)
+ end
+end
+
+
+function ReplayTestScene:onInputPress()
+ SCENE = ReplaySelectScene()
+end
+
+return ReplayTestScene
\ No newline at end of file
diff --git a/scene/stick_config.lua b/scene/stick_config.lua
new file mode 100644
index 0000000..179139b
--- /dev/null
+++ b/scene/stick_config.lua
@@ -0,0 +1,152 @@
+local StickConfigScene = SCENE:extend()
+StickConfigScene.title = "Controller Config"
+
+local configurable_inputs = {
+ "menu_decide",
+ "menu_back",
+ "left",
+ "right",
+ "up",
+ "down",
+ "rotate_left",
+ "rotate_left2",
+ "rotate_right",
+ "rotate_right2",
+}
+
+local input_names = {
+ menu_decide='Confirm Selection',
+ menu_back = 'Go Back',
+ left='Left',
+ right='Right',
+ up='Up',
+ down='Down',
+ rotate_left='Rotate Counter-clockwise',
+ rotate_left2='Rotate Counter-clockwise (2)',
+ rotate_right='Rotate Clockwise',
+ rotate_right2='Rotate Clockwise (2)'
+}
+
+local function newSetInputs()
+ local set_inputs = {}
+ for i, input in ipairs(configurable_inputs) do
+ set_inputs[input] = false
+ end
+ return set_inputs
+end
+
+function StickConfigScene:new()
+ self.input_state = 1
+ self.set_inputs = newSetInputs()
+ self.new_input = {}
+ self.axis_timer = 0
+end
+
+function StickConfigScene:update()
+end
+
+function StickConfigScene:render()
+ MainBackground()
+ for i, input in ipairs(configurable_inputs) do
+ drawText(input_names[input], 40, 50 + i * 20, 200, "left")
+ if self.set_inputs[input] then
+ drawText(self.set_inputs[input], 240, 50 + i * 20, 300, "left")
+ end
+ end
+ if self.input_state > #configurable_inputs then
+ drawText("Press enter/Confirm Selection to confirm, delete/backspace to retry" .. (SETTINGS.input and ", escape/Go Back to cancel" or ""), 0, 0, 1000)
+ else
+ drawText("Press joystick input for " .. input_names[configurable_inputs[self.input_state]] .. ", tab to skip, escape to cancel", 0, 0, 1000)
+ end
+
+ self.axis_timer = self.axis_timer + 1
+end
+
+local function addJoystick(input, name)
+ if not input[name] then
+ input[name] = {}
+ end
+end
+
+function StickConfigScene:onInputPress(e)
+ if e.type == "key" then
+ -- function keys, escape, and tab are reserved and can't be remapped
+ if e.scancode == "escape" or (self.input_state > #configurable_inputs and e.input == "menu_back") then
+ SCENE = InputConfigScene()
+ elseif self.input_state > #configurable_inputs then
+ if e.scancode == "return" or e.input == "" then
+ -- save new input, then load next scene
+ local had_config = SETTINGS.input ~= nil
+ if not SETTINGS.input then SETTINGS.input = {} end
+ SETTINGS.input.joysticks = self.new_input
+ SCENE = had_config and InputConfigScene() or TitleScene()
+ elseif e.scancode == "delete" or e.scancode == "backspace" then
+ -- retry
+ self.input_state = 1
+ self.set_inputs = newSetInputs()
+ self.new_input = {}
+ end
+ else -- Other keys - skip
+ self.set_inputs[configurable_inputs[self.input_state]] = "skipped"
+ self.input_state = self.input_state + 1
+ end
+ elseif string.sub(e.type, 1, 3) == "joy" then
+ if self.input_state <= #configurable_inputs then
+ if e.type == "joybutton" then
+ addJoystick(self.new_input, e.name)
+ if not self.new_input[e.name].buttons then
+ self.new_input[e.name].buttons = {}
+ end
+ if self.new_input[e.name].buttons[e.button] then return end
+ self.set_inputs[configurable_inputs[self.input_state]] =
+ "jbtn " ..
+ e.button ..
+ " " .. string.sub(e.name, 1, 10) .. (string.len(e.name) > 10 and "..." or "")
+ self.new_input[e.name].buttons[e.button] = configurable_inputs[self.input_state]
+ self.input_state = self.input_state + 1
+ elseif e.type == "joyaxis" then
+ if (e.axis ~= self.last_axis or self.axis_timer > 30) and math.abs(e.value) >= 1 then
+ addJoystick(self.new_input, e.name)
+ if not self.new_input[e.name].axes then
+ self.new_input[e.name].axes = {}
+ end
+ if not self.new_input[e.name].axes[e.axis] then
+ self.new_input[e.name].axes[e.axis] = {}
+ end
+ if (
+ self.new_input[e.name].axes[e.axis][e.value >= 1 and "positive" or "negative"]
+ ) then return end
+ self.set_inputs[configurable_inputs[self.input_state]] =
+ "jaxis " ..
+ (e.value >= 1 and "+" or "-") .. e.axis ..
+ " " .. string.sub(e.name, 1, 10) .. (string.len(e.name) > 10 and "..." or "")
+ self.new_input[e.name].axes[e.axis][e.value >= 1 and "positive" or "negative"] = configurable_inputs[self.input_state]
+ self.input_state = self.input_state + 1
+ self.last_axis = e.axis
+ self.axis_timer = 0
+ end
+ elseif e.type == "joyhat" then
+ if e.direction ~= "c" then
+ addJoystick(self.new_input, e.name)
+ if not self.new_input[e.name].hats then
+ self.new_input[e.name].hats = {}
+ end
+ if not self.new_input[e.name].hats[e.hat] then
+ self.new_input[e.name].hats[e.hat] = {}
+ end
+ if self.new_input[e.name].hats[e.hat][e.direction] then
+ return
+ end
+ self.set_inputs[configurable_inputs[self.input_state]] =
+ "jhat " ..
+ e.hat .. " " .. e.direction ..
+ " " .. string.sub(e.name, 1, 10) .. (string.len(e.name) > 10 and "..." or "")
+ self.new_input[e.name].hats[e.hat][e.direction] = configurable_inputs[self.input_state]
+ self.input_state = self.input_state + 1
+ end
+ end
+ end
+ end
+end
+
+return StickConfigScene
diff --git a/scene/title.lua b/scene/title.lua
new file mode 100644
index 0000000..d8579fe
--- /dev/null
+++ b/scene/title.lua
@@ -0,0 +1,144 @@
+local TitleScene = SCENE:extend()
+
+TitleScene.title = "Title"
+TitleScene.restart_message = false
+
+local main_menu_screens = {
+ NameEntryScene,
+ ReplaySelectScene,
+ TrainingScene,
+ InputConfigScene,
+ FullscreenScene,
+ MusicToggleScene,
+ LinesToggleScene,
+ ExitScene,
+}
+
+function TitleScene:new()
+ VCTRL.clearAll() -- Reset the RESTART button state
+ VCTRL.new(SETTINGS.input.virtual)
+ if SOUNDS['bgm_firsthalf']:isPlaying() or SOUNDS['bgm_secondhalf']:isPlaying() or not SETTINGS["music"] then
+ love.audio.stop()
+ end
+ self.main_menu_state = 1
+ PENTO_MODE = false
+ self.code = {0,0,0,0,0,0,0,0}
+ VCTRL.toggle(false)
+end
+
+function TitleScene:update()
+end
+
+function TitleScene:drawCredits(top, left)
+ love.graphics.setColor(0,0,0,0.7)
+ love.graphics.rectangle("fill", top, left, 335, 345, 10, 10)
+ drawText("Design & Programming mycophobia ", 6+top, 6+left, 1000, "left")
+ drawText("Programming Cambridge contributors", 6+top, 21+left, 1000, "left")
+ drawText("Music Jerry Martin", 6+top, 36+left, 1000, "left")
+ drawText(" Juraj Stanik", 6+top, 51+left, 1000, "left")
+ drawText("RNG Consultant colour_thief", 6+top, 66+left, 1000, "left")
+ drawText("Mac Launcher nightmareci", 6+top, 81+left, 1000, "left")
+ drawText("People Who Tested switchpalacecorner", 6+top, 106+left, 1000, "left")
+ drawText("and/or Offered esquatre", 6+top, 121+left, 1000, "left")
+ drawText("Cool Suggestions Kirby703", 6+top, 136+left, 1000, "left")
+ drawText(" netdoll", 6+top, 151+left, 1000, "left")
+ drawText(" lindtobias", 6+top, 166+left, 1000, "left")
+ drawText(" zaphod77", 6+top, 181+left, 1000, "left")
+ drawText(" Arch Nemesis", 6+top, 196+left, 1000, "left")
+ drawText(" dtet_enjoyer", 6+top, 211+left, 1000, "left")
+ drawText(" woozy", 6+top, 226+left, 1000, "left")
+ drawText(" AgentBasey", 6+top, 241+left, 1000, "left")
+ drawText(" Zircean", 6+top, 256+left, 1000, "left")
+ drawText(" Eden GT", 6+top, 271+left, 1000, "left")
+ drawText("Special Thanks theabsolute.plus", 6+top, 291+left, 1000, "left")
+ drawText(" FYAD/Imp Zone Collective", 6+top, 306+left, 1000, "left")
+ drawText(" All Version 1 Players", 6+top, 321+left, 1000, "left")
+end
+
+function TitleScene:render()
+ MainBackground()
+ love.graphics.setColor(0,0,0,0.7)
+ love.graphics.rectangle("fill", 14, 174, 260, 210, 10, 10)
+ love.graphics.setColor(0.4, 1, 1, 0.5)
+ love.graphics.rectangle("fill", 20, 198 + 20 * self.main_menu_state, 240, 22)
+ --
+ drawBigText('Tromi', 30, 180, 120, "left")
+ drawText('version 2', 110, 193, 120, "left")
+ --
+ for i, screen in pairs(main_menu_screens) do
+ drawText(screen.title, 40, 200 + 20 * i, 1200, "left")
+ end
+ --
+ drawText(SETTINGS["music"] and "On" or "Off", 230, 320, 1000)
+ drawText(SETTINGS["lines"] and "On" or "Off", 230, 340, 1000)
+
+ love.graphics.setColor(0,0,0,0.7)
+ love.graphics.rectangle("fill", 14, 400, 605, 75, 10, 10)
+ --
+ drawText("mycophobia.org", 20, 405, 1000)
+ drawText("Based on Cambridge - t-sp.in/cambridge", 20, 420, 1000)
+ drawText("Music for Tromi by Jerry Martin, all rights reserved - jerrymartinmusic.com", 20, 435, 1000)
+ drawText("Game backgrounds by Pixabay users Joe_hackney, yokim, Favorisxp, Any_Ann, VisualSkyFX ", 20, 450, 1000)
+ self:drawCredits(300, 40)
+
+ if table.concat(self.code, ',') == '1,1,1,1,-1,-1,-1,-1' then PENTO_MODE = true end
+ if PENTO_MODE then
+ drawBigText('PENT MODE', 30, 100, 120, "left")
+ end
+end
+
+function TitleScene:changeOption(rel)
+ local len = #main_menu_screens
+ self.main_menu_state = (self.main_menu_state + len + rel - 1) % len + 1
+end
+
+function TitleScene:onInputPress(e)
+ if e.type == "touch" then
+ if -- PENTO mode
+ e.x >= 14 and
+ e.y >= 40 and
+ e.x <= 274 and
+ e.y <= 160
+ then
+ if e.x >= 137 then -- Right
+ table.remove(self.code, 8)
+ table.insert(self.code, 1, 1)
+ else -- Left
+ table.remove(self.code, 8)
+ table.insert(self.code, 1, -1)
+ end
+ else -- Select option from menu
+ local selecting = math.floor((e.y - 198) / 20)
+
+ if (e.x >= 20 and e.x <= 260) and
+ (selecting > 0 and selecting <= #main_menu_screens)
+ then
+ if self.main_menu_state ~= selecting then
+ self.main_menu_state = selecting
+ else
+ VCTRL.toggle(true)
+ SCENE = main_menu_screens[selecting]()
+ end
+ end
+ end
+ else
+ if e.input == "menu_decide" or e.input == "rotate_left" or e.scancode == "return" then
+ SCENE = main_menu_screens[self.main_menu_state]()
+ elseif e.input == "up" or e.scancode == "up" then
+ self:changeOption(-1)
+ elseif e.input == "down" or e.scancode == "down" then
+ self:changeOption(1)
+ elseif e.input == "left" or e.scancode == "left" then
+ table.remove(self.code, 8)
+ table.insert(self.code, 1, -1)
+ elseif e.input == "right" or e.scancode == "right" then
+ table.remove(self.code, 8)
+ table.insert(self.code, 1, 1)
+ end
+ end
+end
+
+function TitleScene:onInputRelease(e)
+end
+
+return TitleScene
diff --git a/scene/touch_config.lua b/scene/touch_config.lua
new file mode 100644
index 0000000..3e65bb5
--- /dev/null
+++ b/scene/touch_config.lua
@@ -0,0 +1,280 @@
+---@diagnostic disable: cast-local-type
+local TouchConfigScene = SCENE:extend()
+TouchConfigScene.title = "Touchscreen config"
+
+local Grid = require 'game.grid'
+
+local buttonList
+local sliderList
+---@class VCTRL.data
+local focusingButton
+---@type number
+local snapUnit = 1
+---@type boolean
+local hasChanged
+
+---@type function
+local function exitSceneFunc(saved)
+ VCTRL.release()
+ BUTTON.release(buttonList)
+ if SETTINGS.firstTime and not saved then
+ SCENE = InputConfigScene(true)
+ else
+ SCENE = TitleScene()
+ SETTINGS.firstTime = false
+ end
+end
+
+buttonList = {
+ showToggle = BUTTON.new{
+ text = function()
+ if focusingButton then
+ return focusingButton.show and "[SHOW]\nHide" or "Show\n[HIDE]"
+ else
+ return "Show\nHide"
+ end
+ end,
+ x = 275, y = 5, w = 50, h = 75,
+ codeWhenReleased = function ()
+ if focusingButton then
+ focusingButton.show = not focusingButton.show
+ hasChanged = true
+ end
+ end,
+ update = function(self) self.textColor = focusingButton and {1, 1, 1} or {0.5, 0.5, 0.5} end
+ },
+ previewToggle = BUTTON.new{
+ text = "Preview\nON",
+ x = 570, y = 35, w = 60, h = 40,
+ codeWhenReleased = function()
+ VCTRL.release()
+ BUTTON.release(buttonList)
+ SCENE = TouchConfigPreviewScene()
+ end
+ },
+ resetAll = BUTTON.new{
+ text = "RESET\nALL",
+ x = 570, y = 80, w = 60, h = 40,
+ codeWhenReleased = function()
+ local selection = love.window.showMessageBox(
+ "Save config?", "Are you really sure about RESETTING ALL touchscreen configuration?",
+ {"Yes", "No", escapebutton = 2, enterbutton = 1},
+ "info", true
+ )
+ if selection == 1 then
+ VCTRL.focus = nil; focusingButton = nil
+ hasChanged = false
+ VCTRL.clearAll()
+ VCTRL.new(SETTINGS.__default__.input.virtual)
+ SETTINGS.input.virtual = SETTINGS.__default__.input.virtual
+ end
+ end
+ },
+ menuScreen = BUTTON.new{
+ text = "MENU",
+ x = 570, y = 5, w = 60, h = 25,
+ codeWhenReleased = function()
+ if hasChanged or SETTINGS.firstTime then
+ local selection = love.window.showMessageBox(
+ "Save config?", "Do you want to save your changes before exiting?",
+ {"Save", "Discard", "Keep editing", escapebutton = 3, enterbutton = 1},
+ "info", true
+ )
+ if selection == 1 then
+ SETTINGS.input.virtual = VCTRL.exportAll()
+ -- love.window.showMessageBox("Saved!", "Your changes was saved!")
+
+ exitSceneFunc(true)
+ elseif selection == 2 then
+ VCTRL.clearAll()
+ VCTRL.new(SETTINGS.input.virtual)
+ -- love.window.showMessageBox("Discarded!", "Your changes was discarded!")
+
+ exitSceneFunc()
+ end
+ else
+ exitSceneFunc()
+ end
+ end
+ }
+}
+sliderList = {}
+sliderList.opacity = newSlider(
+ 155, 20+5, 120, 100, 0, 100,
+ function()
+ local v
+ if focusingButton then
+ v = math.roundUnit(sliderList.opacity.value, 0.01)
+ if focusingButton.alpha~=v then
+ focusingButton.alpha = v
+ hasChanged = true
+ end
+ sliderList.opacity.value = v
+ end
+ end,
+ {width = 30}
+)
+sliderList.size = newSlider(
+ 155, 60+2.5, 120, 45, 0, 120,
+ function(v)
+ if focusingButton then
+ local v = math.roundUnit(v, 5)
+ if focusingButton.r ~= v then
+ focusingButton.r = v
+ hasChanged = true
+ end
+ sliderList.size.value = v / 120
+ end
+ end,
+ {width = 30}
+)
+local gridSizeTable = {1, 2, 5, 10, 20, 50, 100}
+sliderList.gridSize = newSlider(
+ 405, 50, 100, 1, 1, #gridSizeTable - 1,
+ function()
+ local v = math.roundUnit(sliderList.gridSize.value, 1 / 6)
+ sliderList.gridSize.value = v
+ snapUnit = gridSizeTable[math.roundUnit(v * (#gridSizeTable - 1) + 1)]
+ end,
+ {width = 30}
+); sliderList.gridSize.forceLight = true
+
+local function sliderList_draw()
+ for _, s in pairs(sliderList) do
+ if s.forceLight then
+ love.graphics.setColor(1, 1, 1)
+ else
+ love.graphics.setColor(focusingButton and {1, 1, 1} or {0.5, 0.5, 0.5})
+ end
+ love.graphics.setLineWidth(1)
+ s:draw()
+ end
+end
+
+local function sliderList_update()
+ local x, y
+ if #love.touch.getTouches() == 1 then
+ x, y = GLOBAL_TRANSFORM:inverseTransformPoint(love.touch.getPosition(love.touch.getTouches()[1]))
+ else
+ x, y = GLOBAL_TRANSFORM:inverseTransformPoint(love.mouse.getPosition())
+ end
+ for _, s in pairs(sliderList) do
+ s:update(x, y, #love.touch.getTouches() == 1 or love.mouse.isDown(1))
+ end
+end
+
+function TouchConfigScene:new()
+ VCTRL.toggle(true)
+
+ VCTRL.focus = nil
+ focusingButton = nil
+
+ Grid:new(10, 20)
+ -- TODO
+end
+function TouchConfigScene:update()
+ -- TODO
+ if VCTRL.focus~=focusingButton then
+ focusingButton = VCTRL.focus
+ sliderList.opacity.value = focusingButton.alpha
+ sliderList.size.value = focusingButton.r / 120
+ end
+
+ BUTTON.update(buttonList)
+ sliderList_update()
+end
+
+function TouchConfigScene:render()
+ MainBackground()
+
+ if snapUnit >= 5 then
+ local x1, y1 = GLOBAL_TRANSFORM:inverseTransformPoint(0, 0)
+ local x2, y2 = GLOBAL_TRANSFORM:inverseTransformPoint(love.graphics.getDimensions())
+
+ love.graphics.setColor(1,1,1,math.sin(love.timer.getTime()*4)*.1+.25)
+ love.graphics.setLineWidth(1)
+ -- From 0 to X
+ for i=x1, x2+snapUnit, snapUnit do
+ local x = i - i % snapUnit
+ love.graphics.line(x, y1, x, y2)
+ end
+ -- From 0 to Y
+ for i=y1,y2+snapUnit,snapUnit do
+ local y= i - i % snapUnit
+ love.graphics.line(x1, y, x2, y)
+ end
+ end
+
+ love.graphics.setColor(0, 0, 0, 0.7)
+ -- Opacity and Size
+ love.graphics.rectangle("fill", 10, 5, 267, 75)
+ -- Snap to grid
+ love.graphics.rectangle("fill", 330, 5, 150, 75)
+
+ -- Opacity
+ drawText("Opacity", 20, 15, 100, "left")
+ drawText(string.format("%3.1d%%", focusingButton and focusingButton.alpha * 100 or 0), 225, 15, 40, "left")
+ -- Size
+ drawText("Size", 20, 55, 100, "left")
+ drawText(string.format("%3.1dpx", focusingButton and focusingButton.r or 0), 225, 55, 40, "left")
+ -- Snap to grid
+ drawText(string.format("Snap to grid: %3s", snapUnit), 345, 15, 140, "left")
+
+ for _, v in ipairs(VCTRL) do
+ if v ~= focusingButton then
+ v:draw(
+ focusingButton and
+ (v.show and 0.5 or 0.1) or
+ (v.show and 1 or 0.5)
+ )
+ end
+ end
+ if focusingButton then
+ focusingButton:draw(
+ math.clamp(
+ math.sin(love.timer.getTime()*4)*.5+0.1,
+ focusingButton.show and 1 or 0.1, 1
+ )
+ )
+ end
+
+ sliderList_draw()
+ BUTTON.draw(buttonList)
+end
+
+---@param e SCENE_onInput
+function TouchConfigScene:onInputMove(e)
+ if e.type == "touch" or (e.type == "mouse" and love.mouse.isDown(1)) then
+ if VCTRL.drag(e.dx, e.dy, e.id or 1) then hasChanged = true end
+ end
+
+ if e.type == "mouse" then
+ BUTTON.checkHovering(buttonList, e.x, e.y)
+ end
+end
+---@param e SCENE_onInput
+function TouchConfigScene:onInputPress(e)
+ if e.type == "mouse" or e.type == "touch" then
+ if not (
+ VCTRL.press(e.x, e.y, e.id and e.id or 1, true) or
+ BUTTON.press(buttonList, e.x, e.y, e.id) or
+ (e.x >= 80 and e.x <= 230 and e.y >= 10 and e.y <= 77)
+ ) then
+ VCTRL.focus = nil
+ focusingButton = nil
+ end
+ end
+end
+---@param e SCENE_onInput
+function TouchConfigScene:onInputRelease(e)
+ if e.type == "mouse" or e.type == "touch" then
+ if not BUTTON.release(buttonList, e.x, e.y, e.id) then
+ if focusingButton and VCTRL.release(e.id or 1) then
+ focusingButton.x = math.roundUnit(focusingButton.x, snapUnit)
+ focusingButton.y = math.roundUnit(focusingButton.y, snapUnit)
+ end
+ end
+ end
+end
+
+return TouchConfigScene
\ No newline at end of file
diff --git a/scene/touch_config_preview.lua b/scene/touch_config_preview.lua
new file mode 100644
index 0000000..eb6814e
--- /dev/null
+++ b/scene/touch_config_preview.lua
@@ -0,0 +1,93 @@
+local TouchConfigPreviewScene = SCENE:extend()
+TouchConfigPreviewScene.title = "Touchscreen config (Preview)"
+
+local Grid = require 'game.grid'
+local GameMode = require 'game.gamemode'
+
+local buttonList
+buttonList = {
+ previewToggle = BUTTON.new{
+ text = "Preview\nOFF",
+ x = 570, y = 35, w = 60, h = 40,
+ codeWhenReleased = function()
+ VCTRL.release()
+ BUTTON.release(buttonList)
+ SCENE = TouchConfigScene()
+ end
+ },
+}
+local sliderList = {}
+
+local secret_grade_grid = {}
+do
+ local colour_names = {'R', 'O', 'Y', 'G', 'C', 'B', 'M'}
+ local color_blocks = {E = {colour = "", flash = 0, skin = "2tie"}}
+ for _, v in pairs(colour_names) do
+ color_blocks[v] = {colour = v, flash = 0, skin = "2tie"}
+ end
+
+ for y = 20, 1, -1 do
+ local hole_pos = (y > 10 and 21 - y) or (y > 1 and y - 1) or 2
+ local current_colour_name = colour_names[(20 - y)%7+1]
+ local row = {}
+ for x = 1, 10 do
+ if x == hole_pos then
+ row[x] = color_blocks.E
+ else
+ row[x] = color_blocks[current_colour_name]
+ end
+ end
+ secret_grade_grid[y] = row
+ end
+end
+
+function TouchConfigPreviewScene:new()
+ VCTRL.toggle(true)
+
+ VCTRL.focus = nil
+ Grid:new(10, 20)
+ Grid.grid = secret_grade_grid
+ -- TODO
+end
+function TouchConfigPreviewScene:update()
+ BUTTON.update(buttonList)
+end
+
+function TouchConfigPreviewScene:render()
+ if not BACKGROUNDS[0]:isPlaying() then
+ BACKGROUNDS[0]:play()
+ end
+ if BACKGROUNDS[0]:tell() >= 0.5 then
+ BACKGROUNDS[0]:rewind()
+ end
+ love.graphics.setColor(0.7, 0.7, 0.7)
+ love.graphics.draw(BACKGROUNDS[0])
+
+ GameMode.drawFrame{grid = {width = 10, height = 20}}
+ Grid:draw(false, 1)
+
+ BUTTON.draw(buttonList)
+ VCTRL.draw()
+end
+
+---@param e SCENE_onInput
+function TouchConfigPreviewScene:onInputMove(e)
+ if e.type == "mouse" then
+ BUTTON.checkHovering(buttonList, e.x, e.y)
+ end
+end
+---@param e SCENE_onInput
+function TouchConfigPreviewScene:onInputPress(e)
+ if e.type ~= "virtual" and e.input == 'menu_back' then SCENE = InputConfigScene() end
+ if e.type == "mouse" or e.type == "touch" then
+ BUTTON.press(buttonList, e.x, e.y, e.id)
+ end
+end
+---@param e SCENE_onInput
+function TouchConfigPreviewScene:onInputRelease(e)
+ if e.type == "mouse" or e.type == "touch" then
+ BUTTON.release(buttonList, e.x, e.y, e.id)
+ end
+end
+
+return TouchConfigPreviewScene
\ No newline at end of file
diff --git a/scene/training.lua b/scene/training.lua
new file mode 100644
index 0000000..328331b
--- /dev/null
+++ b/scene/training.lua
@@ -0,0 +1,79 @@
+local TrainingScene = SCENE:extend()
+TrainingScene.title = "Max Gravity Training"
+
+local menuKey
+
+function TrainingScene:new()
+ menuKey = BUTTON.new{
+ text = "MENU",
+ x = 265, y = 0, w = 60, h = 25,
+ codeWhenReleased = function() SCENE = TitleScene() end
+ }
+
+ game_mode = require 'game.gamemode'
+ if PENTO_MODE then
+ ruleset = require 'game.rotation_pent'
+ else
+ ruleset = require 'game.rotation'
+ end
+ self.retry_mode = game_mode
+ self.retry_ruleset = ruleset
+ -- self.secret_inputs = inputs
+ self.game = game_mode()
+ self.ruleset = ruleset(self.game)
+ self.game:initialize(self.ruleset)
+ self.inputs = {
+ left=false,
+ right=false,
+ up=false,
+ down=false,
+ rotate_left=false,
+ rotate_left2=false,
+ rotate_right=false,
+ rotate_right2=false,
+ }
+ self.paused = false
+end
+
+function TrainingScene:update()
+ local inputs = {}
+ for input, value in pairs(self.inputs) do
+ inputs[input] = value
+ end
+ self.game:update(inputs, self.ruleset)
+ self.game.grid:update()
+end
+
+function TrainingScene:render()
+ self.game:draw(self.paused)
+ menuKey:draw()
+ VCTRL.draw()
+end
+
+function TrainingScene:onInputPress(e)
+ if e.type == "mouse" or (e.type == "touch" and not VCTRL.press(e.x, e.y, e.id)) then
+ menuKey:press(e.x, e.y, e.id)
+ elseif (self.game.game_over or self.game.completed) and (e.input == "menu_decide" or e.input == "menu_back" or e.input == "rotate_right") and self.game.game_over_frames > 50 then
+ SCENE = TitleScene()
+ elseif (e.input == "menu_back") then
+ SCENE = TitleScene()
+ elseif e.input == "restart" or e.input == "menu_decide" then
+ SCENE = TrainingScene()
+ elseif e.input and string.sub(e.input, 1, 5) ~= "menu_" then
+ self.inputs[e.input] = true
+ end
+end
+
+function TrainingScene:onInputRelease(e)
+ if e.type == "mouse" or (e.type == "touch" and not VCTRL.release(e.id)) then
+ menuKey:release(e.x, e.y, e.id)
+ elseif e.input and string.sub(e.input, 1, 5) ~= "menu_" then
+ self.inputs[e.input] = false
+ end
+end
+
+function TrainingScene:onInputMove(e)
+ menuKey._hovering = menuKey:isHovering(e.x, e.y)
+end
+
+return TrainingScene
diff --git a/screenshot/Replay_screen_differences.png b/screenshot/Replay_screen_differences.png
new file mode 100644
index 0000000..1222155
Binary files /dev/null and b/screenshot/Replay_screen_differences.png differ
diff --git a/screenshot/SPOILER_pento_code.png b/screenshot/SPOILER_pento_code.png
new file mode 100644
index 0000000..cc57e5b
Binary files /dev/null and b/screenshot/SPOILER_pento_code.png differ
diff --git a/screenshot/SPOILER_tv_code.png b/screenshot/SPOILER_tv_code.png
new file mode 100644
index 0000000..c6767f1
Binary files /dev/null and b/screenshot/SPOILER_tv_code.png differ
diff --git a/settings.lua b/settings.lua
new file mode 100644
index 0000000..25b7a61
--- /dev/null
+++ b/settings.lua
@@ -0,0 +1,49 @@
+local bitser = require 'libs.bitser'
+local fs = love.filesystem
+
+local _settings = fs.read(CONFIG_FILE) ~= nil and FILE.read(CONFIG_FILE) or {}
+local _defaultSettings = {
+ firstTime = true,
+
+ fullscreen = false,
+ music = true,
+ lines = true,
+
+ ---@class input
+ ---@field keys table
+ ---@field joysticks table
+ ---@field touch table
+ input = {
+ keys = {},
+ joysticks = {},
+ virtual = {
+ {type='button',x= 70,y=280,key= 'up',r=45,iconSize=60,alpha=0.4},
+ {type='button',x= 70,y=430,key= 'down',r=45,iconSize=60,alpha=0.4},
+ {type='button',x= -5,y=355,key= 'left',r=45,iconSize=60,alpha=0.4},
+ {type='button',x= 145,y=355,key= 'right',r=45,iconSize=60,alpha=0.4},
+ {type='button',x=640- -5,y=355,key= 'rotate_left',r=45,iconSize=60,alpha=0.4},
+ {type='button',x=640-145,y=355,key= 'rotate_left2',r=45,iconSize=60,alpha=0.4},
+ {type='button',x=640- 70,y=430,key= 'rotate_right',r=45,iconSize=60,alpha=0.4},
+ {type='button',x=640- 70,y=280,key='rotate_right2',r=45,iconSize=60,alpha=0.4},
+ {type='button',x=320, y=420,key= 'restart',r=35,iconSize=60,alpha=0.4},
+ }
+ },
+ tvMode = false -- 79338732
+}
+
+SETTINGS = setmetatable(
+ {__default__ = _defaultSettings},
+ {
+ __index = function(_, k)
+ if _settings[k] == nil then
+ _settings[k] = _defaultSettings[k]
+ FILE.write(CONFIG_FILE,_settings)
+ end
+ return _settings[k]
+ end,
+ __newindex = function(_, k, v)
+ _settings[k] = v
+ FILE.write(CONFIG_FILE,_settings)
+ end
+ }
+)
\ No newline at end of file